`[!]` (exclamation mark in brackets) wildcard in bash

Solution 1:

Citing man 7 glob:

An  expression  "[!...]"  matches  a single character, namely any character
that is not matched by the expression obtained by removing the first '!' from it.
(Thus, "[!]a-]" matches any single character except ']', 'a' and '-'.)

Emphasis on the single character part here, [] can not be used for whole strings in Bash Pattern Matching.


bash's Brace Expansion can be used to match strings, however there is no way (I know of) to negate them. E.g.

1.{gif,csv,mp3}

is expanded to

1.gif 1.csv 1.mp3

no matter whether these files exist.


As wchargin pointed out shopt can be used to enable extended pattern matching operators, one of them being !(). After running shopt -s extglob, e.g.

1.!(gif|csv|mp3)

is expanded to

1.jpg 1.bmp 1.png

if those files exist in the current directory. For more read man bash (section EXPANSION/Pathname Expansion) and Pathname expansion (globbing).


For what you're trying to do I'd always use find, which offers the negation and much more, e.g. in the following way:

find . -maxdepth 1 -type f ! -name "*.gif" -a ! -name "*.csv" -a ! -name "*.mp3"

This just lists the findings, if you want to remove them just append the -delete option to the end of the command. As always with removing actions: Always check carefully first.

find offers a ton of useful options very well explained by man find.

Solution 2:

I think you misunderstood the use of brackets. [abc] matches one of the characters a, b or c. [!abc] matches one character that is not a, b or c. So the commmand

rm myfile [192]

will remove myfile and the files 1, 9 and 2 because you have a space between myfile and the bracket. In contrast, the command

rm myfile[192]

will remove the files myfile1, myfile9 and myfile2.

Solution 3:

Brackets [ ] denote a character class. Any single character inside them may match in the given position. So

file[192]

matches three possible names:

file1
file2
file9

It does not match file192, because it only deals with the single character after file.

We can also use ranges in brackets. [0-9] matches any single digit in the given position. For two digits, we need [0-9][0-9]. For a digit followed by a capital letter, we could use [0-9][A-Z]...

! indicates negation.

file[!192]

will match files that have any single character after file except 1 or 9 or 2. For example it will match file7 and files and file% (and many others) but not file192, because that has two extra characters.

For your use case, I suggest using find instead of [!]. It has a not operator.

It's also recursive, which means it goes into all subdirectories by default. You can limit it to the current directory with -maxdepth 1, if that's what you want. Shell wildcards (globbing) is non-recursive (although recursive globbing with ** can be enabled).

Assuming you don't want to avoid deleting symlinks, we can add -type d to the negated tests to avoid deleting any directories. Thanks to Eliah Kagan for that suggestion.

find /path -maxdepth 1 -not \( -type d -or -iname "*.gif" -or -iname "*.csv" -or -iname "*.mp3" \)

If you see what you want, you can run the command again with -delete

find /path -maxdepth 1  -not \( -type d -or -iname "*.gif" -or -iname "*.csv" -or -iname "*.mp3" \) -delete