I know you can use the find
command for this simple job, but I got an assignment not to use find
or ls
and do the job. How can I do that?

- 46,058
- 19
- 106
- 116

- 195
- 1
- 1
- 7
-
What are you supposed to use to do this? Your own shell script, a C program, Java...? If you let us know what you *can* use, help should be more forthcoming :-) – Grundlefleck Jan 28 '10 at 11:46
-
You might be able to use `echo *` or variations on that to emulate `ls`. – Mark Byers Jan 28 '10 at 11:49
10 Answers
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

- 327,991
- 56
- 259
- 343
-
1why don't you downvote the one with the tree command as well? or the one with echo, those are valid answers as well. – ghostdog74 Jan 28 '10 at 12:05
-
This will fail if a directory is empty, i.e. it will print `dir: bla/*` since `bla/*` is seen as a literal as it has no descendants. You can either use `pushd/popd`, or add `if [[ "$1/*" = "$i" ]]; then continue; fi` as the first line after `do`. – Bogdan Oct 18 '15 at 20:47
-
This could also be fixed (to not emit `bla/*`) by use of `shopt -s nullglob`, or `[ -e "$i" ] || [ -L "$i" ] || continue` inside the loop. – Charles Duffy Aug 13 '18 at 16:07
Try using
tree -d

- 30,779
- 11
- 72
- 106
-
1
-
2I didn't give you the -1, but it's obvious to me why you got it. Why not give as answer `alias bla ls ; bla`? The question that was asked was to write the algorithm, not find some *command* which does the same thing as `ls` but is not called `ls`. – vladr Mar 07 '10 at 04:50
-
7@Vlad Romascanu: Oh... ok, but from "I got an assignment not to use find or ls and do the job" I understood that everything was good enough, except find and ls ^^ – Alberto Zaccagni Mar 08 '10 at 09:09
-
On the other hand maybe his assignment has to run on Solaris or AIX or HPUX, not Linux. :) Let me assure you there is no `tree` command available by default on non-Linuces etc. – vladr Mar 08 '10 at 14:38
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
.)

- 65,483
- 18
- 129
- 130
-
-
@ghostdog -- not portable. `.*` (or `.?*` to skip `.` off the bat) does the trick just fine and is portable too. – vladr Mar 07 '10 at 04:52
Since it is for bash, it is a surprise that this hasn't been already said:
(globstar valid from bash 4.0+)
shopt -s globstar nullglob dotglob
echo **/*/
That's all.
The trailing slash /
is there to select only dirs.
Option globstar
activates the **
(search recursivelly).
Option nullglob
removes an *
when it matches no file/dir.
Option dotglob
includes files that start wit a dot (hidden files).
The du
command will list subdirectories recursively.
I'm not sure if empty directories get a mention, though

- 14,808
- 4
- 33
- 50
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.
-
2As an alternative to `echo`, `for` will glob, so OP can use `for file in * ; do ... ; done`. – outis Mar 07 '10 at 04:48
$ 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

- 41,819
- 10
- 94
- 108
-
1-1 if you're going to provide a complete answer to a homework question, make it a correct one. what about spaces in directory entry names? – just somebody Jan 28 '10 at 12:05
-
and it does not enter directories and does not list text files – 18446744073709551615 Feb 21 '13 at 11:13
-
it DOES enter directories, and the OP said nothing about text files (or any files apart from directories). I'm not going to fix the 'directories with spaces' issue. – Alex Brown Mar 06 '13 at 07:57
-
Too bad. This was almost a great answer, but lacks the information I need (as a n00b) to tinker with it. Looks like just another string of bash-foo to me. – wybe Aug 24 '17 at 15:06
-
So you don’t like an answer written in bash to a question asking for bash because it’s too bashy? This may not be the site for you. – Alex Brown Aug 24 '17 at 15:10
-
Not sure about bashy, but it's definitely *buggy*. Needs more quotes to correctly handle names with spaces. Less substantively, using ksh rather than POSIX function declaration syntax is [not great form](http://wiki.bash-hackers.org/scripting/obsolete). – Charles Duffy Aug 13 '18 at 16:09
-
@CharlesDuffy the form I used is listed as correct on the page you listed. – Alex Brown Aug 14 '18 at 17:40
Technically, neither find nor ls are used by find2perl|perl or File::Find directly.
$ find2perl -type d | perl $ perl -MFile::Find -e'find(sub{-d&&print"$File::Find::name\n"},".")'

- 198,619
- 38
- 280
- 391
-
but then, if that's the case, using Python/Ruby/PHP etc any language that can list files will be technically not using `ls` or `find` – ghostdog74 Mar 07 '10 at 05:49
-
@ghostdog74 Obviously. But since all the serious answers have gotten less than amazing feedback, this is something... less serious. (I was actually going to write up a clone of `find` in C and a shell script which compiles+runs it, but decided it was too much effort for a joke.) – ephemient Mar 07 '10 at 05:55
Based on this answer; use shell options for the desired globbing behaviour:
- enable
**
withglobstar
(Bash 4.0 or newer) - include hidden directories with
dotglob
- expand to the empty string instead of
**/*/
if there is no match withnullglob
and then use printf
with the %q
formatting directive to quote directory names with special characters in them:
shopt -s globstar dotglob nullglob
printf '%q\n' **/*/
so if you have directories like has space
or even containing a newline, you'd get output like
$ printf '%q\n' **/*/
$'has\nnewline/'
has\ space/
with one directory per line.

- 46,058
- 19
- 106
- 116
I wrote a another solution iterative instead of recursive.
iterativePrintDir() {
dirs -c;
currentd=$1
if [ ! -d $currentd ];then
echo "Diretorio não encontrado. \"$currentd\""
fi
pushd $(readlink -f $currentd)
while popd 2>&-; do
echo $currentd
for d in $(dir $currentd); do
test -d "${currentd}/${d}" && pushd "${currentd}/${d}"
done
currentd=$(dirs -l +0)
done
}

- 91
- 8