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