Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a few directories, some with a depth of 3, which contain mixed file types. What I need to do is to rm -rf all the subdirectories that do not contain filetype foo.

Is this achievable with find somehow? I do know that I can use find like this:

find . ! -name '*.foo' -delete

to delete all files within the directories that do not contain any file of type *.foo. Now, how can I use this, to not only delete all unwanted files, but all directories and subdirectories which do not contain *.foo?

share|improve this question
So if you have /a/b, and b has no *.foo files, but /a/b/c has *.foo files, obviously we don't want to run rm -rf /a/b, right? In this case, I don't think rm -rf is the right approach, maybe we need something like rm dir/*; rmdir dir. – Mikel Mar 1 '11 at 3:20
If a directory contains some.foo and some.bar, should it be deleted? Your question is not clear in this respect. – Gilles Mar 1 '11 at 23:31

(Your question is not clear: if a directory contains some.foo and some.bar, should it be deleted? I interpreted it as requiring such a directory to be kept.)

The following script should work, provided that no file name contains a newline and no directory matches *.foo. The principle is to traverse the directory from the leaves up (-depth), and as *.foo files are encountered, the containing directory and all parents are marked as protected. Any reached file that is not *.foo and not protected is a directory to be deleted. Because of the -depth traversal order, a directory is always reached after the *.foo files that might protect it. Warning: minimally tested, remove the echo at your own risk.

find . -depth -name '*.foo' -o -type d | awk '{
    if ($0 ~ /\.foo$/) {
        while (sub("/+[^/]+$", "")) {protect[$0]=1;}
    } else {
        if (!protect[$0]) {
            gsub("[\\\001-/]", "\\\\&"); # protect file names for xargs
}' | xargs echo rm -rf

For once, I'm not proposing a zsh solution.

share|improve this answer
Maybe File::Find would be the right tool for this job. – Gilles Mar 1 '11 at 23:27
I get an error on awk script line 5 running that, but it seems OK if I change [\\\011-/] to [\011-]. – Mikel Mar 2 '11 at 1:28
@Mikel: That's weird, I get no error under Gawk, Mawk or the original awk, and I don't see what could be wrong. Your regexp "[\001-]" (I assume \011 is a typo) matches only \001 and -, which is no good since the point is to protect \\'" and whitespace. – Gilles Mar 2 '11 at 7:58
Yes, sorry, I meant \001. – Mikel Mar 2 '11 at 8:53

I'm not quite sure this can be done using only find, but I think we can do it using only bash and find.

    # return true (0) as soon as we find a "*.foo" file
    find "$1" -type f -name "*.foo" -print0 |
        read -r -d $'\0' file && return 0

    return 1

find . -depth -type d -print0 |
while read -r -d $'\0' dir; do
    if ! tree_contains_foo_files "$dir"; then
        rm -rf "$dir"

Given this test tree:


I get this result:

rm -rf ./dir2/dir2.1
rm -rf ./dir2
rm -rf ./dir3

which I think is what you want, i.e. don't delete dir1, because dir1/dir1.1/dir1.1.1 contains file.foo.

But note that it does process directories multiple times, so it might be slow for large trees. If efficiency is important, I'd use a more powerful programming language.

share|improve this answer
There are more efficient implementations of tree_contains_foo_files, such as [ -n "$(find … | head -c 1)" ], or (faster but requires GNU find) [ -n "$(find … -printf a -quit)" ]. – Gilles Mar 1 '11 at 23:34
Thanks for the suggestion. You're right I should short-circuit as soon as a file is found. Seeing as I'm using read already, how about find ... | read file && return 0. I think that is very slightly faster again. – Mikel Mar 2 '11 at 1:21
Or of course find ... | read -n 1 .... – Mikel Mar 2 '11 at 1:31

IIUYC, you can simply first remove all unwanted files using

find . ! -type f -name '*.foo' -delete

which may empty some directories. Then you can remove the empty directories (and directories containing only empty directories, etc.) like in my question

find . -depth -mindepth 1 -empty -type d -exec rmdir -p -- {} +
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.