I know you can use find command for this simple job. But I got an assignment not to use find or ls and do the job. Please help....
views:
416answers:
7The du
command will list subdirectories recursively.
I'm not sure if empty directories get a mention, though
Like Mark Byers said you can use echo *
to get a list of all files in the current directory.
The test
or []
command/builtin has an option to test if a file is a directory.
Apply recursion and you're done.
you can do it with just the shell
#!/bin/bash
recurse() {
for i in "$1"/*;do
if [ -d "$i" ];then
echo "dir: $i"
recurse "$i"
elif [ -f "$i" ]; then
echo "file: $i"
fi
done
}
recurse /path
OR if you have bash 4.0
#!/bin/bash
shopt -s globstar
for file in /path/**
do
echo $file
done
$ function f { for i in $1/*; do if [ -d $i ]; then echo $i; f $i; fi; done }
$ mkdir -p 1/2/3 2/3 3
$ f .
./1
./1/2
./1/2/3
./2
./2/3
./3
Below is one possible implementation:
# my_ls -- recursively list given directory's contents and subdirectories
# $1=directory whose contents to list
# $2=indentation when listing
my_ls() {
# save current directory then cd to "$1"
pushd "$1" >/dev/null
# for each non-hidden (i.e. not starting with .) file/directory...
for file in * ; do
# print file/direcotry name if it really exists...
test -e "$file" && echo "$2$file"
# if directory, go down and list directory contents too
test -d "$file" && my_ls "$file" "$2 "
done
# restore directory
popd >/dev/null
}
# recursively list files in current
# directory and subdirectories
my_ls .
As an exercise you can think of how to modify the above script to print full paths to files (instead of just indented file/dirnames), possibly getting rid of pushd
/popd
(and of the need for the second parameter $2
) in the process.
Incidentally, note the use of test XYZ && command
which is fully equivalent to if test XYZ ; then command ; fi
(i.e. execute command
if test XYZ
is successful). Also note that test XYZ
is equivalent to [ XYZ ]
, i.e. the above is also equivalent to if [ XYZ ] ; then command ; fi
. Also note that any semicolon ;
can be replaced with a newline, they are equivalent.
Remove the test -e "$file" &&
condition (only leave the echo
) and see what happens.
Remove the double-quotes around "$file"
and see what happens when the directory whose contents you are listing contains filenames with spaces in them. Add set -x
at the top of the script (or invoke it as sh -x scriptname.sh
instead) to turn on debug output and see what's happenning in detail (to redirect debug output to a file, run sh -x scriptname.sh 2>debugoutput.txt
).
To also list hidden files (e.g. .bashrc
):
...
for file in * .?* ; do
if [ "$file" != ".." ] ; then
test -e ...
test -d ...
fi
done
...
Note the use of !=
(string comparison) instead of -ne
(numeric comparison.)
Another technique would be to spawn subshells instead of using pushd
/popd
:
my_ls() {
# everything in between roundbrackets runs in a separatly spawned sub-shell
(
# change directory in sub-shell; does not affect parent shell's cwd
cd "$1"
for file in ...
...
done
)
}
Note that on some shell implementations there is a hard limit (~4k) on the number of characters which can be passed as an argument to for
(or to any builtin, or external command for that matter.) Since the shell expands, inline, *
to a list of all matching filenames before actually performing for
on it, you can run into trouble if *
is expanded inside a directory with a lot of files (same trouble you'll run into when running, say ls *
in the same directory, e.g. get an error like Command too long
.)
Cheers, V.