[ACCEPTED]-How do I recursively list all directories at a location, breadth-first?-ksh

Accepted answer
Score: 36

The find command supports -printf option which recognizes 6 a lot of placeholders.

One such placeholder 5 is %d which renders the depth of given path, relative 4 to where find started.

Therefore you can use 3 following simple one-liner:

find -type d -printf '%d\t%P\n' | sort -r -nk1 | cut -f2-

It is quite straightforward, and 2 does not depend on heavy tooling like perl.

How 1 it works:

  • it internally generates list of files, each rendered as a two-field line
  • the first field contains the depth, which is used for (reverse) numerical sorting, and then cut away
  • resulting is simple file listing, one file per line, in the deepest-first order
Score: 24

If you want to do it using standard tools, the 8 following pipeline should work:

find . -type d | perl -lne 'print tr:/::, " $_"' | sort -n | cut -d' ' -f2

That is,

  1. find and print all the directories here in depth first order
  2. count the number of slashes in each directory and prepend it to the path
  3. sort by depth (i.e., number of slashes)
  4. extract just the path.

To 7 limit the depth found, add the -maxdepth 6 argument to the find command.

If you want 5 the directories listed in the same order 4 that find output them, use "sort -n -s" instead 3 of "sort -n"; the "-s" flag stabilizes the 2 sort (i.e., preserves input order among 1 items that compare equally).

Score: 8

You can use find command, find /path/to/dir 2 -type d So below example list of directories 1 in current directory :

find . -type d
Score: 7

My feeling is that this is a better solution 8 than previously mentioned ones. It involves 7 grep and such and a loop, but I find it 6 works very well, specifically for cases 5 where you want things line buffered and 4 not the full find buffered.

It is more resource 3 intensive because of:

  • Lots of forking
  • Lots of finds
  • Each directory before the current depth is hit by find as many times as there is total depth to the file structure (this shouldn't be a problem if you have practically any amount of ram...)

This is good because:

  • It uses bash and basic gnu tools
  • It can be broken whenever you want (like you see what you were looking for fly by)
  • It works per line and not per find, so subsequent commands don't have to wait for a find and a sort
  • It works based on the actual file system separation, so if you have a directory with a slash in it, it won't be listed deeper than it is; if you have a different path separator configured, you still are fine.
#!/bin/bash 
depth=0

while find -mindepth $depth -maxdepth $depth | grep '.'
do
    depth=$((depth + 1))
done

You 2 can also fit it onto one line fairly(?) easily:

depth=0; while find -mindepth $depth -maxdepth $depth | grep --color=never '.'; do depth=$((depth + 1)); done

But 1 I prefer small scripts over typing...

Score: 6

I don't think you could do it using built-in 5 utilities, since when traversing a directory 4 hierarchy you almost always want a depth-first 3 search, either top-down or bottom-up. Here's 2 a Python script that will give you a breadth-first 1 search:

import os, sys

rootdir = sys.argv[1]
queue = [rootdir]

while queue:
    file = queue.pop(0)
    print(file)
    if os.path.isdir(file):
        queue.extend(os.path.join(file,x) for x in os.listdir(file))

Edit:

  1. Using os.path-module instead of os.stat-function and stat-module.
  2. Using list.pop and list.extend instead of del and += operators.
Score: 3

I tried to find a way to do this with find but 15 it doesn't appear to have anything like 14 a -breadth option. Short of writing a patch for 13 it, try the following shell incantation 12 (for bash):

LIST="$(find . -mindepth 1 -maxdepth 1 -type d)";
while test -n "$LIST"; do
    for F in $LIST; do
        echo $F;
        test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)";
    done;
    LIST=$NLIST;
    NLIST="";
done

I sort of stumbled upon this 11 accidentally so I don't know if it works 10 in general (I was testing it only on the 9 specific directory structure you were asking 8 about)

If you want to limit the depth, put 7 a counter variable in the outer loop, like 6 so (I'm also adding comments to this one):

# initialize the list of subdirectories being processed
LIST="$(find . -mindepth 1 -maxdepth 1 -type d)";
# initialize the depth counter to 0
let i=0;
# as long as there are more subdirectories to process and we haven't hit the max depth
while test "$i" -lt 2 -a -n "$LIST"; do
    # increment the depth counter
    let i++;
    # for each subdirectory in the current list
    for F in $LIST; do
        # print it
        echo $F;
        # double-check that it is indeed a directory, and if so
        # append its contents to the list for the next level
        test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)";
    done;
    # set the current list equal to the next level's list
    LIST=$NLIST;
    # clear the next level's list
    NLIST="";
done

(replace 5 the 2 in -lt 2 with the depth)

Basically this 4 implements the standard breadth-first search 3 algorithm using $LIST and $NLIST as a queue of directory 2 names. Here's the latter approach as a one-liner 1 for easy copy-and-paste:

LIST="$(find . -mindepth 1 -maxdepth 1 -type d)"; let i=0; while test "$i" -lt 2 -a -n "$LIST"; do let i++; for F in $LIST; do echo $F; test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)"; done; LIST=$NLIST; NLIST=""; done
Score: 2

Without the deserved ordering: find 5 -maxdepth -type d

To get the deserved ordering, you 4 have to do the recursion yourself, with 3 this small shellscript:

#!/bin/bash
r () 
{
    let level=$3+1
    if [ $level -gt $4 ]; then return 0; fi
    cd "$1"
    for d in *; do
        if [ -d "$d" ]; then
            echo $2/$d
        fi;
    done
    for d in *; do
        if [ -d "$d" ]; then
            (r "$d" "$2/$d" $level $4)
        fi;
    done
}
r "$1" "$1" 0 "$2"

Then you can call 2 this script with parameters base directory 1 and depth.

Score: 1

Here's a possible way, using find. I've 1 not thoroughly tested it, so user beware...

depth=0
output=$(find . -mindepth $depth -maxdepth $depth -type d | sort); 
until [[ ${#output} -eq 0 ]]; do 
  echo "$output"
  let depth=$depth+1
  output=$(find . -mindepth $depth -maxdepth $depth -type d | sort)
done
Score: 0

Something like this:

find . -type d | 
  perl -lne'push @_, $_;
    print join $/,
      sort { 
        length $a <=> length $b || 
          $a cmp $b 
        } @_ if eof'

0

More Related questions