[ACCEPTED]-How do I recursively list all directories at a location, breadth-first?-ksh
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
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,
- find and print all the directories here in depth first order
- count the number of slashes in each directory and prepend it to the path
- sort by depth (i.e., number of slashes)
- 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).
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
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...
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:
- Using
os.path
-module instead ofos.stat
-function andstat
-module. - Using
list.pop
andlist.extend
instead ofdel
and+=
operators.
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
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.
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
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
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.