215

How can I remove all empty directories in a subtree? I used something like

find . -type d -exec rmdir {} 2>/dev/null \;

but I needs to be run multiple times in order to remove directories containing empty directories only. Moreover, it's quite slow, especially under cygwin.

1

8 Answers 8

322

Combining GNU find options and predicates, this command should do the job:

find . -type d -empty -delete
  • -type d restricts to directories
  • -empty restricts to empty ones
  • -delete removes each directory

The tree is walked from the leaves without the need to specify -depth as it is implied by -delete.

8
  • 3
    -delete already implies -depth so you don't need to specify that manually.
    – jamadagni
    Sep 19, 2014 at 2:31
  • 2
    Thanks, I didn't realize that. Answer updated. Sep 19, 2014 at 15:57
  • 21
    I would add -mindepth 1 here, to prevent from deleting the starting directory itself, if it would be empty. Aug 20, 2016 at 12:49
  • 2
    Great, but doesn't work on my old SunOS hosts...
    – dokaspar
    Aug 8, 2017 at 10:05
  • 2
    ! has a special meaning for the shell. You need to escape it. Something like: \! -name 'Completed' just before -delete should work. Or you just put a marker file in this directory. Apr 2, 2018 at 8:34
60

List the directories deeply-nested-first.

find . -depth -type d -exec rmdir {} \; 2>/dev/null

(Note that the redirection applies to the find command as a whole, not just to rmdir. Redirecting only for rmdir would cause a significant slowdown as you'd need to invoke an intermediate shell.)

You can avoid running rmdir on non-empty directories by passing the -empty predicate to find. GNU find tests the directory when it's about to run the command, so directories that have just been emptied will be picked up.

find . -depth -type d -empty -exec rmdir {} \;

Another way to speed up would be to group the rmdir invocations. Both are likely to be noticeably faster than the original, especially under Cygwin. I don't expect much difference between these two.

find . -depth -type d -print0 | xargs -0 rmdir 2>/dev/null
find . -depth -type d -exec rmdir {} + 2>/dev/null

Which method is faster depends on how many non-empty directories you have. You can't combine -empty with methods for grouping invocations, because then the directories that only contain empty directories aren't empty by the time find looks at them.

Another method would be to run multiple passes. Whether this is faster depends on a lot of things, including whether the whole directory hierarchy can remain in the disk cache between find runs.

while [ -n "$(find . -depth -type d -empty -print -exec rmdir {} +)" ]; do :; done

Alternatively, use zsh. The glob qualifier F matches non-empty directories, so /^F matches empty directories. Directories that only contain empty directories can't be matched so easily.

while rmdir **/*(/N^F); do :; done

(This terminates when rmdir receives an empty command line.)

8
  • That's it. Instead of 90 seconds it takes 0.90 s.
    – maaartinus
    Mar 1, 2011 at 22:14
  • @maaartinus: I'm curious: do you have a similar data set where you could try without -p? I wouldn't have thought it would make a difference. Mar 1, 2011 at 22:15
  • 3
    @maartinus — other little optimizations: adding -empty should work with this one (although I'm not sure exactly how much it'll gain). And very, very trivially, since you probably don't want to remove ., use -mindepth 1.
    – mattdm
    Mar 1, 2011 at 22:19
  • It was not the removal but the process start up overhead, what took nearly all the time. I had overlooked the -depth argument, which makes rmdir -p useless. I've changed my comment already. The 90 s was my original attempt; there's nothing surprising here.
    – maaartinus
    Mar 1, 2011 at 22:20
  • 2
    I realized we can remove the rmdir command call altogether, at least with GNU find, with this command: find . -depth -type d -empty -delete Jan 2, 2014 at 14:26
7

find . -depth -type d -exec rmdir {} +

is the simplest and standard compliant answer to this question.

The other answers given here unfortunately all depend on vendor specific enhancements that do not exist on all systems.

4
  • 4
    This answer raises an error for each directory that cannot be deleted which may be less than desirable. Apr 13, 2017 at 0:04
  • @WillemvanKetwich, add a 2> /dev/null to hide those. Jul 7, 2022 at 15:16
  • It will also fail if you deal with a huge amount of directories, as there is a limit on how many characters rmdir can accept. Using \; instead of + will fix this issue, but it will make the command significantly slower. Mar 16 at 16:47
  • Also all these solutions will fail with hidden files, like .DS_Store on Mac or Desktop.ini on Windows. Mar 18 at 2:35
6

If you just tack a -p on your rmdir, that'll work in one pass. It won't be pretty or optimal, but it should get everything. That tells rmdir to remove any non-empty parent directories of the one you're removing.

You can save a little bit by adding the -empty test to find, so it doesn't bother with non-empty directories.

1

I use these aliases for frequently used find commands, especially when I cleaning up disk space using dupeguru, where removing duplicates can result in a lot of empty directories.

Comments inside .bashrc so I won't forget them later when I need to tweak it.

# find empty directories
alias find-empty='find . -type d -empty'

# fine empty/zero sized files
alias find-zero='find . -type f -empty'

# delete all empty directories!
alias find-empty-delete='find-empty -delete'

# delete empty directories when `-delete` option is not available.
# output null character (instead of newline) as separator. used together
# with `xargs -0`, will handle filenames with spaces and special chars.
alias find-empty-delete2='find-empty -print0 | xargs -0 rmdir -p'

# alternative version using `-exec` with `+`, similar to xargs.
# {}: path of current file
# +: {} is replaced with as many pathnames as possible for each invocation.
alias find-empty-delete3='find-empty -exec rmdir -p {} +'

# for removing zero sized files, we can't de-dupe them automatically
# since they are technically all the same, so they are typically left
# beind. this removes them if needed.
alias  find-zero-delete='find-zero -delete'
alias find-zero-delete2='find-zero -print0 | xargs -0 rm'
alias find-zero-delete3='find-zero -exec rm {} +'
0

find . -type d -printf "%d %p\n" |\ sort -nr |\ perl -pe 's/^\d+\s//;' |\ while read dir; do \ (rmdir "$dir" > /dev/null 2>&1); \ done

Here's how it works:

  1. Recursively list all the directories along with their depth
  2. Sort by descending order of their depth
  3. Filter out only the directory paths
  4. Run rmdir on the list one by one
0

Found a simple but effective solution which should be also POSIX compliant:

while find /path/to -type d -empty -print -exec rmdir {} + | grep -q ^; do :; done

It's looping a find command which executes rmdir -v in every round. If no more empty dirs are found, no output is generated, grep returns negative and breaks the loop.

One solution of https://unix.stackexchange.com/a/8433/496909 is quite similar. It's utilizing a subshell and this a grep. Don't know which one is better.

PS: If you're interested in the removed directories just remove -q.

1
  • -empty is not POSIX. busybox can be built with -empty supported and -delete not though Jul 7, 2022 at 15:22
-2

rm -r */ command worked easily for me. rm should require -f to forcefully remove directories with files. rm -r should only remove empty directories. I'm open to why this could be wrong. This should also leave files since */ only looks at folders.

2
  • 1
    I'd strongly recommend to test it thoroughly first as rm is primarily meant to remove files. While */ only matches directories, I have no idea what it does on deeper levels. I can also imagine that it works on some systems only.
    – maaartinus
    Jun 22, 2018 at 19:30
  • 1
    Slippery slope in its finest. Mar 18 at 2:36

You must log in to answer this question.

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