275

Using GNU bash (version 4.0.35(1)-release (x86_64-suse-linux-gnu), I would like to negate a test with Regular Expressions. For example, I would like to conditionally add a path to the PATH variable, if the path is not already there, as in:

TEMP=/mnt/silo/bin
if [[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH; else PATH=$PATH:$TEMP; fi
TEMP=/mnt/silo/Scripts:
if [[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH; else PATH=$PATH:$TEMP; fi
TEMP=/mnt/silo/local/bin
if [[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH; else PATH=$PATH:$TEMP; fi
export PATH

I'm sure there are a million ways to do this, but what I would like to know is if the conditional can be negated somehow, as in (the erroneous):

TEMP=/mnt/silo/bin
if ![[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH:$TEMP; fi
TEMP=/mnt/silo/Scripts:
if ![[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH:$TEMP; fi
TEMP=/mnt/silo/local/bin
if ![[ ${PATH} =~ ${TEMP} ]] ; then PATH=$PATH:$TEMP; fi
export PATH
codeforester
  • 39,467
  • 16
  • 112
  • 140
David Rogers
  • 4,010
  • 3
  • 29
  • 28

5 Answers5

316

You had it right, just put a space between the ! and the [[ like if ! [[

SiegeX
  • 135,741
  • 24
  • 144
  • 154
  • 26
    Oye vey! Just when I safely sidestep the intergalactic special character madness of perl, I find myself lost in bash space (placement)! (I feel fear squeezing my gut like a python.) Thanks! – David Rogers Dec 28 '10 at 16:37
  • 4
    Are you sure that it's always before `[[` and not inside, like `if [[ ! "$value" =~ $regex ]];`? For example, Sublime Text highlights it weirdly if outside: https://i.imgur.com/AQWuFtf.png – Artfaith Apr 08 '21 at 00:24
  • intelliJ gives this warning; You are missing a required space after the !. See [SC1035](https://github.com/koalaman/shellcheck/wiki/SC1035). – Richard Tyler Miles Sep 07 '22 at 17:43
  • 1
    @RichardTylerMiles this answer *does* have the space after the '!'. You can also put the '!' inside the double-brackets, I just prefer it outside. – SiegeX Sep 08 '22 at 22:59
187

You can also put the exclamation point inside the brackets:

if [[ ! $PATH =~ $temp ]]

but you should anchor your pattern to reduce false positives:

temp=/mnt/silo/bin
pattern="(^|:)$temp(:|$)"
if [[ ! $PATH =~ $pattern ]]

which looks for a match at the beginning or end with a colon before or after it (or both). I recommend using lowercase or mixed case variable names as a habit to reduce the chance of name collisions with shell variables.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Ah, thanks for the reminder about anchoring. The idea of using lowercase or mixed variable names is confusing to a bash beginner, since the advice I have seen so far is to use uppercase. I understand the point you are making, but I have not seen enough bash scripting examples to feel comfortable deviating from (my understanding of) the cookbook. – David Rogers Dec 28 '10 at 16:33
  • 5
    @anyoneis trust us on this one. Use of user-defined uppercase variables should be avoided. All variables in bash are expanded with `$` so there is no reason to uppercase them to make them stand out. – SiegeX Jan 01 '11 at 19:53
  • I find `if [[ ! $foo =~ bar ]]` safer than `if ! [[ $foo =~ bar ]]`, because it makes easier to introduce more conditions to the `if` – Cristian Todea Jun 27 '17 at 13:48
27

the safest way is to put the ! for the regex negation within the [[ ]] like this:

if [[ ! ${STR} =~ YOUR_REGEX ]]; then

otherwise it might fail on certain systems.

Guildencrantz
  • 1,875
  • 1
  • 16
  • 30
11

Yes you can negate the test as SiegeX has already pointed out.

However you shouldn't use regular expressions for this - it can fail if your path contains special characters. Try this instead:

[[ ":$PATH:" != *":$1:"* ]]

(Source)

Community
  • 1
  • 1
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 2
    Another reason to use this form it that it won't accidentally match substrings (e.g. fail to add "/bin" to the path because "/usr/bin" is already there). – Gordon Davisson Dec 28 '10 at 00:54
  • It took me a while to understand how the colons on the left gave me the anchoring I wanted. the idea of reducing the pattern by adding material to the string to be searched is worth remembering. I don't understand why special characters in the path would disrupt the regex solution but not the bash pattern solution. Can you give me an example? – David Rogers Dec 28 '10 at 17:13
  • I don't think this will work reliably in all cases. Regex matching in superior IMHO – Felipe Alvarez Aug 01 '15 at 01:04
7

I like to simplify the code without using conditional operators in such cases:

TEMP=/mnt/silo/bin
[[ ${PATH} =~ ${TEMP} ]] || PATH=$PATH:$TEMP
dimir
  • 693
  • 6
  • 23