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
Setup
Directory and Script Setup
Please check the first and second post on the tree command to see our directory and script setup.
Issues
- I would like to see the files in my directory, or, at least, have an option that allows me to see the files.
- I can’t see hidden directories (directories starting with a
.
) - I would like a clearer presentation of the structure.
- If a non-existant directory is given as an argument, it should tell us that it does not exist and exit.
Optimizations
Seeing the Files
This should be easy. Let’s first see how the ls -R
command looks like:
[ahmed@amayem .git]$ ls -R
.:
branches config description HEAD hooks info objects refs test.sh
./branches:
./hooks:
applypatch-msg.sample post-update.sample pre-commit.sample pre-rebase.sample
commit-msg.sample pre-applypatch.sample prepare-commit-msg.sample update.sample
./info:
exclude
./objects:
info pack
./objects/info:
./objects/pack:
./refs:
heads tags
./refs/heads:
./refs/tags:
We notice a small problem, the directories are listed more than once. Once inside the parent directory, and once when its contents are listed:
branches config description HEAD hooks info objects refs test.sh
./branches:
We have two branches
listed. Let’s differentiate the contents of each directory using the -p
flag:
-p, --indicator-style=slash
append / indicator to directories
This will add a /
to the directories:
[ahmed@amayem .git]$ ls -Rp
.:
branches/ config description HEAD hooks/ info/ objects/ refs/ test.sh
./branches:
./hooks:
applypatch-msg.sample post-update.sample pre-commit.sample pre-rebase.sample
commit-msg.sample pre-applypatch.sample prepare-commit-msg.sample update.sample
./info:
exclude
./objects:
info/ pack/
./objects/info:
./objects/pack:
./refs:
heads/ tags/
./refs/heads:
./refs/tags:
Great, now let’s get rid of the directories as listed in the parent directory. We will use grep "[^/]$"
to capture all the lines that don’t end with a /
.
[ahmed@amayem .git]$ ls -RF | grep "[^/]$"
.:
config
description
HEAD
test.sh*
./branches:
./hooks:
applypatch-msg.sample*
commit-msg.sample*
post-update.sample*
pre-applypatch.sample*
pre-commit.sample*
prepare-commit-msg.sample*
pre-rebase.sample*
update.sample*
./info:
exclude
./objects:
./objects/info:
./objects/pack:
./refs:
./refs/heads:
./refs/tags:
I would like to use sed
now to add the beginning dashes, but I have a problem. How do I figure out how many dashes to put before a file name? We figured out how many dashes to put before a directory based on the number of slashes, /
in its pathname, e.g. I can replace objects/
in ./objects/info:
with two dashes. After some digging around I found this command:
[ahmed@amayem .git]$ ls -1 */*
hooks/applypatch-msg.sample
hooks/commit-msg.sample
hooks/post-update.sample
hooks/pre-applypatch.sample
hooks/pre-commit.sample
hooks/prepare-commit-msg.sample
hooks/pre-rebase.sample
hooks/update.sample
info/exclude
objects/info:
objects/pack:
refs/heads:
refs/tags:
I got some of the files with their relative paths, however this command is not recursive. That means that to get the files three levels down I need to run ls -1 */*/*
. It also has the obvious problem of not displaying the files and directories above the requested depth.
Trying find
During my digging around I found out I could use the nifty command find
.
[ahmed@amayem .git]$ find
.
./branches
./hooks
./hooks/prepare-commit-msg.sample
./hooks/pre-applypatch.sample
./hooks/pre-rebase.sample
./hooks/applypatch-msg.sample
./hooks/commit-msg.sample
./hooks/post-update.sample
./hooks/pre-commit.sample
./hooks/update.sample
./info
./info/exclude
./config
./test.sh
./description
./refs
./refs/tags
./refs/heads
./objects
./objects/info
./objects/pack
./HEAD
That’s great. Now we can get rid of the the grep ":$" | sed -e 's/:$//'
portion of the original command.
[ahmed@amayem .git]$ find | sed -e 's/[^-][^/]*//--/g' -e 's/^/ /' -e 's/-/|/'
.
|-branches
|-hooks
|---prepare-commit-msg.sample
|---pre-applypatch.sample
|---pre-rebase.sample
|---applypatch-msg.sample
|---commit-msg.sample
|---post-update.sample
|---pre-commit.sample
|---update.sample
|-info
|---exclude
|-config
|-test.sh
|-description
|-refs
|---tags
|---heads
|-objects
|---info
|---pack
|-HEAD
That’s much better. However, I think we can do better. With this output I can’t tell whether config
for example is a directory or a file. It does not seem that find
has the ability to put a slash, /
, after directories like ls
. I also like the options that ls
has, because it would be more compatible with the tree
command that we are designing, in that it is a listing of contents, while as the find
command searches for files.
Let’s move on to making a larger script that does what I want in the next post
Next steps
Note
We don’t have to add the -1
flag to print everything out in a list, because when ls
output is piped it is automatically printed as a list:
-1 (The numeric digit ``one''.) Force output to be one entry per line. This is the default when output is not to a terminal.
References
- Dem Pilafian on centerkey
- Matthew Scharley‘s and user431529‘s answers to this stackoverflow question.