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

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?

share|improve this question
Check this one. Hope it will help you stackoverflow.com/questions/5659516/… – infaustus Feb 11 '13 at 16:50

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.

share|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

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).

share|improve this answer
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
Thanks for pointing that out, updating the answer. – peterph Feb 11 '13 at 18:08
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
Then, that would be ! -type d. -type f is for regular files only. – Stéphane Chazelas Feb 12 '13 at 6:25

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.