14

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?

2
  • 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 :-) Commented Jan 28, 2010 at 11:46
  • You might be able to use echo * or variations on that to emulate ls.
    – Mark Byers
    Commented Jan 28, 2010 at 11:49

10 Answers 10

39

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
3
  • 1
    why don't you downvote the one with the tree command as well? or the one with echo, those are valid answers as well.
    – ghostdog74
    Commented Jan 28, 2010 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
    Commented Oct 18, 2015 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. Commented Aug 13, 2018 at 16:07
11

Try using

tree -d
4
  • 1
    Why did I get a -1? Comment please. Commented Jan 28, 2010 at 13:28
  • 2
    I 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
    Commented Mar 7, 2010 at 4:50
  • 8
    @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 ^^ Commented Mar 8, 2010 at 9: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
    Commented Mar 8, 2010 at 14:38
5

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.)

2
  • you can use shopt -s dotglob to list hidden files.
    – ghostdog74
    Commented Mar 7, 2010 at 4:42
  • @ghostdog -- not portable. .* (or .?* to skip . off the bat) does the trick just fine and is portable too.
    – vladr
    Commented Mar 7, 2010 at 4:52
5

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).

0
1

The du command will list subdirectories recursively.

I'm not sure if empty directories get a mention, though

0
1

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.

1
  • 2
    As an alternative to echo, for will glob, so OP can use for file in * ; do ... ; done.
    – outis
    Commented Mar 7, 2010 at 4:48
0
$ 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
7
  • 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? Commented Jan 28, 2010 at 12:05
  • and it does not enter directories and does not list text files Commented Feb 21, 2013 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
    Commented Mar 6, 2013 at 7: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
    Commented Aug 24, 2017 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
    Commented Aug 24, 2017 at 15:10
0

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"},".")'
2
  • 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
    Commented Mar 7, 2010 at 5: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
    Commented Mar 7, 2010 at 5:55
0

Based on this answer; use shell options for the desired globbing behaviour:

  • enable ** with globstar (Bash 4.0 or newer)
  • include hidden directories with dotglob
  • expand to the empty string instead of **/*/ if there is no match with nullglob

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.

0

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
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.