Delete all files except one

A couple of days I was asked if knew an easy way to delete all but one files in a directory. If you didn't already guess it from this blog entry's title, there is a simple way - or to be more precise - there are several ways. The first one is quiet straightforward and uses the find command:

find . -not -name do_not_delete_me -delete

This works recursively and also preserves files named do_not_delete_me contained in sub-folders of the current directory:

user@host /tmp/test $ ls -R
.:
a  b  c  do_not_delete_me  foo

./a:
foo

./b:
bar  do_not_delete_me

./c:
baz
user@host /tmp/test $ find . -not -name do_not_delete_me -delete
find: cannot delete `./b': Directory not empty
user@host /tmp/test $ ls -R
.:
b  do_not_delete_me

./b:
do_not_delete_me

As you can see, find tries to delete the folder b but fails because the folder is not empty. If you don't care for files in sub-directories, it gets a bit more complicated with find:

find . -mindepth 1 -maxdepth 1 -not -name do_not_delete_me -exec rm -rf -- {} +

The -mindepth/-maxdepth parameters tell find to ignore sub-directories, because we're not interested in their contents. This should also save some execution time - especially if the directory hierarchy is really deep.

While this works well, Bash's pattern matching offers an easier solution for this:

rm -rf !(do_not_delete_me)

As the manpage explains, the text enclosed by brackets is considered to be a pattern list, i.e. constructs like !(*.jpg|*.png) are perfectly valid. If you don't care for files in sub-directories, this might be the preferred way - it's shorter and maybe even faster than the solutions using find.

No matter which solution you choose, refrain from error-prone constructs like rm -rf `ls | grep -v do_not_delete_me`.