Unix - delete files and folders excluding PATTERN
Recently I was faced with the task of deleting all the files/folders in a directory excluding those matching a specific pattern. So I cooked up a single-line unix command to do the work. Must it be only one line? I suppose not, but it's definitely cooler that way!
While the problem is pretty simple, I was a little surprised at how complex my solution ended up being. Here's the command I used; NOTE: this is a poor solution because it doesn't handle filenames containing line-feed characters (which didn't matter in my situation).
ls | grep -v PATTERN | xargs -n1 -IREPLACE rm -rf REPLACE
I did not use the "find" command because I do not want to recurse into folders matching PATTERN. For example, consider the following file structure:
file_foo.txt
first_dir
|
+--> contents
+--> ...
foo_dir
|
+--> anotherfile.txt
+--> morefiles.log
foo_file.txt
somefile.txt
Using pattern "foo" must only remove "first_dir" (and it's contents of course) and "somefile.txt" (not "anotherfile.txt" or "morefiles.log").
Question, are there better (more elegant and correct) ways to accomplish this?
EDIT:
It was recently brought to my attention that "find" may be a better option:
find * -maxdepth 0 ! -name PATTERN -print0 | xargs -0n1 rm -rf
This solution does correctly handle paths containing line-feed characters.
The following examples have echo
prefixed so that you can test the patterns before actually using them. Remove the echo
to activate the rm -rf
. Substitute rm -ri
to prompt for confirmation.
ksh has a negative match extension to its globbing:
# ksh
echo rm -rf !(*foo*)
The same syntax is available in bash if you set the extglob
option:
# bash
shopt -s extglob
echo rm -rf !(*foo*)
zsh has its own syntax for this:
# zsh
setopt extended_glob
echo rm -rf ^*foo*
It can also use the ksh-style syntax:
# zsh: ksh-style glob syntax
setopt ksh_glob no_bare_glob_qual
echo rm -rf !(*foo*)
# zsh: ksh-style glob syntax, adapted for the default bare_glob_qual option
setopt ksh_glob bare_glob_qual
echo rm -rf (!(*foo*))
Here's another find
solution. I'm not sure this has any real advantage over yours, but it doesn't need xargs
and allows for the rare possibility that * expands to too many names.
find . -maxdepth 1 ! -name PATTERN -type f -delete
I also added -type f
so that it would not attempt to delete directories.
Warning: -delete
is powerful. I gave one of my test files 0 permissions and the command above deleted it without hesitation.