Let's say I have a directory with multiple files, all of which are either binary files (files with no declared extensions) and source files (.c extension). I do this:

$ ls

and get:

README.md hello-world.c hello-world get-name.c get-name

and I want to list only files with a specific extensions (say both .c and .md), I do:

ls *.c *.md

and that's ok!

But what if now I want to delete all files that do not have a .c or .md extension, how can I do this?

I know how to do it to files whose extensions are known to me, like:

ls *.c *.md | xargs rm

But how do I tell the command line to delete the files that DON'T match my criteria?


Use find:

find -maxdepth 1 ! -name "*.c" -print0 | xargs -0 ...

Note that -maxdepth, -print0, -r and -0 are found in a few find implementations (were introduced by GNU find/xargs) but are not specified by POSIX. Another way is, as Stéphane mentioned in his comment, using the -exec action of find - remember to use the -exec command {} + form, as it dramatically reduces the number of invocations of command (it has to allow syntax where the filenames are placed at the end though, or else you need to wrap the command inside a sh -c call).

Alternatively grep the output of ls

ls | grep -v "\.c$" | xargs ...

You can filter several extensions either with regular expressions:

... | grep -v "\.\(c\|md\)$" | ...

with extended regexps:

... | grep -Ev "\.(c|md)$" | ...

with fixed strings option (won't match just at the end of filename!):

... | grep -vF ".c
.md" | ...

or by chaining more greps:

... | grep -v "\.c$" | grep -v "\.md$" | ...

That said, find is probably the best option, since with the -print0 option it handles all possible characters in filenames that might need escaping otherwise (provided your find and xargs are able to work with NULL-terminated strings - e.g. the GNU tools are).

| improve this answer | |
  • 1
    The find version is the only one that will work with all filenames, but it won't work in all versions of find and xargs. The -not is GNU find only. – jordanm Feb 11 '13 at 17:27
  • 1
    In most cases, you can replace xargs with the -exec flag of find. Note that find . -maxdepth 1 ! -name '*.c' returns ., you may want to add -mindepth 1, or use the standard form: find . ! -name . -prune ! -name '*.c' ! -name '*.md' -exec rm -f {} + – Stéphane Chazelas Feb 11 '13 at 20:25
  • @StephaneChazelas actually -type f is probably better in this case, since rm -f {} + will fail with a directory argument. – peterph Feb 11 '13 at 23:06
  • 1
    Then, that would be ! -type d. -type f is for regular files only. – Stéphane Chazelas Feb 12 '13 at 6:25

In ksh, you can use:

rm -- !(*.c|*.md)

Those globbing extensions are also available in bash after shopt -s extglob and in zsh after setopt kshglob.

The following is the zsh native equivalent:

setopt extended_glob
rm -- ^(*.c|*.md)

The ! in an extglob is "not". For your original command, it's preferred to write this:

rm -- *.c *.md

Parsing the output of ls is unreliable, and will break on some filenames. See the bash wiki for parsing ls.

| improve this answer | |
  • Hmm, does not work on ZSH :/ – rodrigoalves Feb 11 '13 at 17:00
  • @rodrigoalvesvieira updated for zsh – jordanm Feb 11 '13 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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