This post is part of an educational series on building a shell script to graphically display the structure of a directory.
Previously
- We broke down Dem Pilafian’s one line command to display a tree of a directory
- We broke down Dem Pilafian’s script that uses the one line command
- We modified the one line to show files, as well as directories
- We built a recursive function that successfully prints out the graphical tree
Goals
- Optimize the previously built recursive function.
Pre-requisites
Getting the code
Run the following command:
git clone https://github.com/amayem/shell-tree.git
In any step past step-0
you can get the code by issuing the following command but changing the step number to the appropriate one:
git checkout -f step-1
Step-13 Replacing the LevelFlags Arrays with a String
A lot of the current code has to do with the levelFlags
array, and generating the string that precedes the actual directory/file name. Also notice that whenever we are listing the contents of one directory they all have the same string prefix. Instead of using the levelFlags
array to generate the prefix string let’s simply pass the prefix string as a parameter to the function as follows:
listdir()
{
local currentPath=$1 prefix=$2
local -a currentDir=($(ls $1))
local -i lastIndex=$((${#currentDir[*]} - 1)) index
for ((index=0; index<lastIndex; index++))
do
printf "%s├─%s\n" $prefix ${currentDir[$index]}
if [ -d "$currentPath/${currentDir[$index]}" ]; then
listdir "$currentPath/${currentDir[$index]}" $prefix" │"
fi
done
if [ $lastIndex -ge 0 ]; then
printf "%s└─%s\n" $prefix ${currentDir[$lastIndex]}
if [ -d "$currentPath/${currentDir[$index]}" ]; then
listdir "$currentPath/${currentDir[$index]}" $prefix" "
fi
fi
}
listdir $PWD ' '
The output is as follows:
[ahmed@amayem .git]$ ./../tree.sh
HEAD├─
config├─
description├─
hooks├─
│├─applypatch-msg.sample
│├─commit-msg.sample
│├─post-update.sample
│├─pre-applypatch.sample
│├─pre-commit.sample
│├─pre-push.sample
│├─pre-rebase.sample
│├─prepare-commit-msg.sample
│└─update.sample
index├─
info├─
│└─exclude
logs├─
│├─HEAD
│└─refs
│├─heads
│└─master
│└─remotes
│└─origin
│├─HEAD
│└─master
objects├─
├─info
│└─pack
│├─pack-5d8c6d23ff13eded7a9d401ff91dae0f7fd6d00d.idx
│└─pack-5d8c6d23ff13eded7a9d401ff91dae0f7fd6d00d.pack
packed-refs├─
refs└─
heads├─
│└─master
remotes├─
│└─origin
│├─HEAD
tags└─
It seems that the contents of the base directory are being displayed before the tree stem, for example: HEAD├─
. This is because the following line
printf "%s├─%s\n" $prefix ${currentDir[$index]}
isn’t registering $prefix
as an actual variable. This is because of the IFS
default. As mentioned in the man
bash
pages:
IFS The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is ``<space><tab><newline>''.
What’s happening is that when we send a space
as a parameter to the function it is not registered as a parameter because it is considered a field separator. What we need to to do is to remove the space from the IFS
.
Step-14 Removing the Space from IFS
oldIFS=$IFS
IFS=$'\t\n'
listdir()
{
local currentPath=$1 prefix=$2
local -a currentDir=($(ls $1))
local -i lastIndex=$((${#currentDir[*]} - 1)) index
for ((index=0; index<lastIndex; index++))
do
printf "%s├─%s\n" $prefix ${currentDir[$index]}
if [ -d "$currentPath/${currentDir[$index]}" ]; then
listdir "$currentPath/${currentDir[$index]}" $prefix"│ "
fi
done
if [ $lastIndex -ge 0 ]; then
printf "%s└─%s\n" $prefix ${currentDir[$lastIndex]}
if [ -d "$currentPath/${currentDir[$index]}" ]; then
listdir "$currentPath/${currentDir[$index]}" $prefix" "
fi
fi
}
listdir $PWD ' '
IFS=$oldIFS
This produces the following:
[ahmed@amayem .git]$ ./../tree.sh
├─COMMIT_EDITMSG
├─HEAD
├─config
├─description
├─hooks
│ ├─applypatch-msg.sample
│ ├─commit-msg.sample
│ ├─post-update.sample
│ ├─pre-applypatch.sample
│ ├─pre-commit.sample
│ ├─pre-push.sample
│ ├─pre-rebase.sample
│ ├─prepare-commit-msg.sample
│ └─update.sample
├─index
├─info
│ └─exclude
├─logs
│ ├─HEAD
│ └─refs
│ ├─heads
│ │ └─master
│ └─remotes
│ └─origin
│ ├─HEAD
│ └─master
├─objects
│ ├─info
│ └─pack
│ ├─pack-5d8c6d23ff13eded7a9d401ff91dae0f7fd6d00d.idx
│ └─pack-5d8c6d23ff13eded7a9d401ff91dae0f7fd6d00d.pack
├─packed-refs
└─refs
├─heads
│ └─master
├─remotes
│ └─origin
│ ├─HEAD
│ └─master
└─tags
Success.
Next Steps
- Allow for flags to be used that are compatible with
ls
- Deal with bad input
- Make it a user friendly bash script