5

There's the find . -type d -empty command, but that only finds literally empty directories.

What I want to achieve is something a little bit more complex: I want to find empty directories or directories that ONLY have other directories or empty directories, and this rule should be passed on recursively.

So for example, in this directory structure: ~1/11/111/ It would find 1/, 1/11/ and 1/11/111/ But if at any point in the tree there's a file, for example in this case if there's a file in 1/11/111/file1 then none of them before the file should be returned. So if there's an empty directory 1/11/111/1111/ next to 1/11/111/file1, 1111 should be returned.

The reason I want this is because I want to delete my empty folders.

Now I could probably do that by running find . -type d -empty -delete over and over again, but I want a way to kind of visualize it before I remove them.

Is this possible?

2 Answers2

5

To see the directories deleted, just insert -print:

find . -type d -empty -print -delete

This would delete any sub-hierarchy beneath the current directory that is empty, while displaying the pathnames of the directories that are deleted.

The -delete action implies -depth, i.e. the traversal will start at the bottom of the directory structure and work its way up (a depth-first-traversal). This means that any directory that may be considered for deletion will already have its sub-directories processed (and these wil have been deleted if they were empty).

Example:

$ mkdir -p 1/2/3/4/5/6
$ tree
.
`-- 1
    `-- 2
        `-- 3
            `-- 4
                `-- 5
                    `-- 6

6 directories, 0 file
$ find . -type d -empty -print -delete
./1/2/3/4/5/6
./1/2/3/4/5
./1/2/3/4
./1/2/3
./1/2
./1
$ tree
.

0 directory, 0 file

If one directory has a file:

$ mkdir -p 1/2/3/4/5/6
$ touch 1/2/3/file
$ tree
.
`-- 1
    `-- 2
        `-- 3
            |-- 4
            |   `-- 5
            |       `-- 6
            `-- file

6 directories, 1 file
$ find . -type d -empty -print -delete
./1/2/3/4/5/6
./1/2/3/4/5
./1/2/3/4
$ tree
.
`-- 1
    `-- 2
        `-- 3
            `-- file

3 directories, 1 file

To list the directories that only contain subdirectories, but without deleting them (using bash):

shopt -s globstar
for dir in ./**/; do
    if [ -z "$(find "$dir" ! -type d -exec echo x \;)" ]; then
        printf '%s\n' "$dir"
    fi
done

This would loop over all the subdirectories in the current directory (reclusively, using the ** shell glob), and then try to find something that is not a directory in any of them. If something is found, then that directory is not empty, otherwise the pathname of the empty directory is outputted.

With GNU find, you could add -quit to the very end of the above find command (after \;) to speed things up a bit.

Kusalananda
  • 333,661
  • Thanks! That's more or less what I'm looking for. Just one more question: since -delete is recursive, is -exec also recursive? Also, since the -print option only seems to work with the -delete option, is there any way to list them all the way I wanted without deleting them? EDIT: Nevermind I can make it do that with the -depth argument. – user361323 Jul 23 '19 at 07:58
  • @user361323 find is recursive over the search paths that you give it. The -delete or -exec will just act on the current pathname being investigated at the moment. To list the empty directories without deleting them, just remove the -delete action (this will list the directories that are empty, but not the ones that contain only directories). – Kusalananda Jul 23 '19 at 08:07
  • @user361323 See added bit at the end. – Kusalananda Jul 23 '19 at 08:19
2

To print the empty directories without deleting any, we'll have to do more than just remove the empty ones from the leaves up.

Something like this may work:

find dir/ -type d ! -exec sh -c \
  'find "$1" ! -type d -print -quit | grep -q . >/dev/null' sh {} \; -print

The outer find finds each directory, and runs another find on each of them. The inner find looks to see if that directory (recursively) contains any non-directories, and returns a true value if so. The outer find then prints each directory that returned false (i.e. didn't contain any non-directories).

That's rather slow, though, with the recursive finds. A better solution might involve caching directories where files were found, or an approach that would start from empty directories and work up until a branch that contains file(s).

ilkkachu
  • 138,973