Delete all files except in a certain subdirectory with find
one way is to use -exec rm
instead of -delete
.
find a \( -name b -prune \) -o -type f -exec rm {} +
alternatively use -not -path
instead of -prune
:
find a -not -path "*/b*" -type f -delete
Explanation why -prune
collides with -delete
:
find complains when you try to use -delete
with -prune
because -delete
implies -depth
and -depth
makes -prune
ineffective.
observe the behaviour of find with and without -depth
:
$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2
There is no guarantee about the order in a single directory. But there is a guarantee that a directory is processed before its contents. Note foo/
before any foo/*
and foo/bar
before any foo/bar/*
.
This can be reversed with -depth
.
$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/
Note that now all foo/*
appear before foo/
. Same with foo/bar
.
more explanation:
-prune
prevents find from descending into a directory. In other words-prune
skips the contents of the directory. In your case-name b -prune
means that when find reaches a directory with the nameb
it will skip the directory including all subdirectories.-depth
makes find to process the contents of a directory before the directory itself. That means by the time find gets to process the directory entryb
its contents has already been processed. Thus-prune
is ineffective with-depth
in effect.-delete
implies-depth
so it can delete the contents first and then the empty directory.-delete
refuses to delete non-empty directories.
Explanation of alternative method:
find a -not -path "*/b*" -type f -delete
This may or may not be easier to remember.
This command still descends into the directory b
and proceses every single file in it only for -not
to reject them. This can be a performance issue if the directory b
is huge.
-path
works differently than -name
. -name
only matches against the name (of the file or directory) while -path
is matching against the entire path. For example observe the path /home/lesmana/foo/bar
. -name bar
will match because the name is bar
. -path "*/foo*"
will match because the string /foo
is in the path. -path
has some intricacies you should understand before using it. Read the man page of find
for more details.
Beware that this is not 100% foolproof. There are chances of "false positives". The way the command is written above it will skip any file which has any parent directory which name is starting with b
(positive). But it will also skip any file which name is starting with b
regardless of position in the tree (false positive). This can be fixed by writing a better expression than "*/b*"
. That is left as an exercise for the reader.
I assume that you used a
and b
as placeholders and the real names are more like allosaurus
and brachiosaurus
. If you put brachiosaurus
in place of b
then the amount of false positives will be drastically reduced.
At least the false positives will be not deleted, so it will be not as tragic. Furthermore, you can check for false positives by first running the command without -delete
(but remember to place the implied -depth
) and examine the output.
find a -not -path "*/b*" -type f -depth
Just use rm
instead of -delete
:
find a -name b -prune -o -type f -exec rm -f {} +