How to find only directories without subdirectories?
To find only those leaf directories that contain files, you can combine an answer of the referenced question https://unix.stackexchange.com/a/203991/330217 or similar questions https://stackoverflow.com/a/4269862/10622916 or https://serverfault.com/a/530328 with find
's ! -empty
find rootdir -type d -links 2 ! -empty
Checking the hard links with -links 2
should work for traditional UNIX file systems. The -empty
condition is not part of the POSIX standard, but should be available on most Linux systems.
According to KamilMaciorowski's comment the traditional link count semantics for directories is not valid for Btrfs. This is confirmed in https://linux-btrfs.vger.kernel.narkive.com/oAoDX89D/btrfs-st-nlink-for-directories which also mentions Mac OS HFS+ as an exception from the traditional behavior. For these file systems a different method is necessary to check for leaf directories.
You could use nested find
and count number of subdirectories:
find . -type d \
\( -exec sh -c 'find "$1" -mindepth 1 -maxdepth 1 -type d -print0 | grep -cz "^" >/dev/null 2>&1' _ {} \; -o -print \)
If the */
filename globbing pattern expands to something that is not the name of a directory, then the current directory has no (non-hidden) subdirectories.
With find
:
find root -type d -exec sh -c 'set -- "$1"/*/; [ ! -d "$1" ]' sh {} \; ! -empty -print
Note that this would treat a symbolic link to a directory in a leaf directory as a directory since the pattern would traverse the symbolic link.
The -empty
predicate is not standard, but often implemented. Without it, you would do something similar as with detecting subdirectories:
find root -type d \
-exec sh -c 'set -- "$1"/*/; [ ! -d "$1" ]' sh {} \; \
-exec sh -c 'set -- "$1"/*; [ -e "$1" ]' sh {} \; -print
Or, a bit more efficiently,
find root -type d -exec sh -c '
dir=$1
set -- "$dir"/*/
[ -d "$1" ] && exit 1
set -- "$dir"/*
[ -e "$1" ]' sh {} \; -print
Or, employing the -links
predicate that I had forgotten about (thanks Bodo):
find root -type d \
-links 2 \
-exec sh -c 'set -- "$1"/*; [ -e "$1" ]' sh {} \; -print