6

If I create the following directory structure:

mkdir -p dir1/dir2/dir3/dir4

then I run find dir1, it returns:

dir1
dir1/dir2
dir1/dir2/dir3
dir1/dir2/dir3/dir4

If I run find dir1 -empty, it returns only:

dir1/dir2/dir3/dir4

showing that only dir4 is empty.

However, if I run find dir1 -empty -delete, all four directories get deleted immediately.

I would expect only dir4 to be deleted, then the next time I ran the command, only dir3 would be deleted, and so on. Why does this happen, and how can I delete only dir4 using find?

0

1 Answer 1

12

Because -delete implies -depth, or depth-first iteration, so find first looks at dir1/dir2/dir3/dir4, notices it's empty, deletes it, then looks at dir1/dir2/dir3, notices it's (now) empty and deletes it...

The GNU manpage says:

-depth
Process each directory's contents before the directory itself. The -delete action also implies -depth.

That's possibly they've considered that it's more commonly useful to be able to delete some files and then possibly the directory containing those files if it was emptied at the same time (-delete doesn't recursively remove the whole subtree), instead of having to repeat the find command until all matching files and directories are gone. Or looking at the other way, removing just the leaves of the tree (as we want here) isn't idempotent: running the command again by accident would strip another layer from the directory tree.

In any case, you could have find run rmdir instead to keep in the default mode, and add -prune to prevent it from trying to descend into the now-deleted directory. And let's add -print for debugging here too, so we see what it removes:

$ mkdir -p dir1/dir2/dir3/dir4
$ find -type d -empty -exec rmdir {} \; -prune -print
./dir1/dir2/dir3/dir4

and this is what remains:

$ find
.
./dir1
./dir1/dir2
./dir1/dir2/dir3

(Here, if the rmdir fails, the -exec action evaluates as falsy and the -prune and -print actions will not run. So the printout to stdout should only contain what was actually removed. This would be different with -exec rmdir {} +.)

Of course that assumes you only wanted to delete empty directories here, if you also want to delete empty files, you could do that first with

find -type f -empty -delete

Or build the awful combination that calls rm for files and rmdir for directories:

find -empty \( -type f -exec rm {} \; -o -type d -exec rmdir {} \; -prune \) -print

(rm -rf would also obviously work, but is riskier.)

2
  • "I'm not exactly sure why it's like that, but it is." - If it wasn't, find -delete -name foo\* on foo1/foo2 wouldn't remove foo1/ because it is non-empty. I'd say that's unexpected behaviour if foo1 only contains foo2.
    – marcelm
    Jun 19, 2021 at 22:08
  • @marcelm, right, I was thinking that -prune could also be an option, but of course that doesn't help there. Probably it's more useful depth-first since it makes the operation more likely to be idempotent, and doesn't result in people ending up running the same command repeatedly to catch all directories they wanted to delete...
    – ilkkachu
    Jun 19, 2021 at 22:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .