How to recursively list subdirectories in Bash without using “find” or “ls” commands?

前端 未结 10 2038
情歌与酒
情歌与酒 2020-12-01 17:22

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 th

相关标签:
10条回答
  • 2020-12-01 17:30

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

    0 讨论(0)
  • 2020-12-01 17:31

    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
    }
    
    0 讨论(0)
  • 2020-12-01 17:42

    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 讨论(0)
  • 2020-12-01 17:44
    $ 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
    
    0 讨论(0)
  • 2020-12-01 17:46

    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
    
    0 讨论(0)
  • 2020-12-01 17:46

    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.

    0 讨论(0)
提交回复
热议问题