In one of my programs I call a bash command to remove all files in the directory, but some:

bash -c
shopt -s extglob
shopt -s nullglob
rm /path/to/directory/!("file1" | "file2")

Which for what I understand mean "Use bash on this command, use the extended pattern, remove all files but those"

It works good, but deletes every file but the ones put in the command.

What I would like is to remove all .mp4 and .zip in the directory but the ones I put in the command.

I think I'll have to add *.mp4 and *.zip somewhere in the command, but I have no idea where.

EDIT: For clarity, what I need is "Delete all .mp4 and .zip files, but keep thoses"

  • have you tried rm /path/to/directory/{*.zip,*.mp4}?
    – Anthon
    Nov 15, 2013 at 9:59
  • Bashism... Bleah. Use find and learn how to make it portable between various system. Why would one need to use bash for such simple goal?
    – jirib
    Nov 15, 2013 at 10:00
  • I am trying with rm /path/to/directory/{*.zip, *.mp4, !("file1" | "file2")
    – DrakaSAN
    Nov 15, 2013 at 10:08
  • @Anthon I get rm: missing operand
    – DrakaSAN
    Nov 15, 2013 at 10:26
  • @Jiri: Because the app is not meant to be portable. Is there other way to delete a file on raspbian?
    – DrakaSAN
    Nov 15, 2013 at 10:27

3 Answers 3


The pattern !("file1" | "file2") matches any file except file1␣ or ␣file2 where is a space. The way you quoted it is misleading: it's equivalent to !(file1 | file2) or !("file1 "|" file2)`.

There's no way in bash to combine a negative pattern with a positive pattern to say “all of these except for those”. In zsh, you can write

setopt extended_glob
rm /path/to/directory/*.(mp4|zip)~(keep*|1234.zip)

to remove this.mp4 and that.zip but not keep-this.mp4 or 1234.zip.

In bash, you can set the GLOBIGNORE variable to exclude certain patterns from all matches.

shopt -s extglob
rm /path/to/directory/*.(mp4|zip)

Alternatively, you can use find, which lets you build arbitrarily complex boolean expressions.

find /path/to/directory \( -name '*.mp4' -o -name '*.zip' \) ! -name 'keep*' ! -name '1234.zip' -delete

Note that find recurses into subdirectories. If this isn't desired, add -maxdepth 1 after /path/to/directory.

If your find doesn't understand -maxdepth (it's common but not standard), use -type d -prune -o instead. If your find doesn't understand -delete (same remark), use -exec rm {} + instead.


There are several ways:

  • Use find (portable as it does not need specific shell [bash])
find . -maxdepth 1 ! -name "exclude" -delete

or in case you have several patterns:

find . -maxdepth 1 ! \( -name "exclude" -o -name "exclude2" \) -exec rm -f {} \+
  • Use GLOBIGNORE variable (bash only)
export GLOBIGNORE=exclude:exclude1:mask*

That will exclude mentioned files and masks from globing. After that you can specify rm your_pattern

You can try it before with ls

  • missing closing bracket
    – jirib
    Nov 15, 2013 at 10:01
  • GNUism. Please be more general and use either 'exec rm' or 'xargs rm'. Thanks.
    – jirib
    Nov 15, 2013 at 10:03
  • I m trying it right now, but it seems it will find all files but thoses I exclude, what I need is to find all .mp4 and .zip but thoses I want to exclude.
    – DrakaSAN
    Nov 15, 2013 at 10:36
  • For find, where is supposed to be the )? (several pattern)
    – DrakaSAN
    Nov 15, 2013 at 10:39
  • @DrakaSAN you can use something like find . -name *.ext ! -name 1.ext
    – rush
    Nov 15, 2013 at 11:59

I would you find. However your problem is the spaces.

rm /path/to/directory/!("file1" | "file2")

is the same as rm "/path/to/directory/!(file1" "|" "file2)"

not rm "/path/to/directory/!(file1|file2)"

read up on the shell quoting rules.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .