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

bashcommand line

I have came across globbing patterns and wildcards and of particular interest to me is [!].

This construct is similar to the [!] construct, except rather than matching any characters inside the brackets, it'll match any character, as long as it is not listed between the [ and ].

rm myfile [!192]

The above I believe will remove any/all files, except for any/all files which have 192 in their name.

I am concerned however about the proper usage of this with a file extension, and in particular multiple conditions.

What would be the proper syntax in such a situation?

rm myfile [!.gif .csv. mp3] 

or

rm myfile [!.gif !.csv !.mp3]

My worry is that the period might be misplaced, and so any file with a . (which would be any of them surely?) would then be manipulated, when I am seeking to cause manipulation of specific files.

This construct is similar to the [ ] construct, except rather than matching any characters inside the brackets, it'll match any character, as long as it is not listed between the [ and ].

(quoted from http://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm)

Now; to me that suggests then a singular ! is sufficient, and all values therein after are contained within the range.

Best Answer

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.