244

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
  • 34,080
  • 14
  • 96
  • 122
David Rogers
  • 3,630
  • 3
  • 26
  • 27

5 Answers5

279

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

SiegeX
  • 128,491
  • 23
  • 138
  • 153
  • 24
    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
  • 2
    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 – Faither Apr 08 '21 at 00:24
170

You can also put the exclamation mark 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.

Community
  • 1
  • 1
Dennis Williamson
  • 324,833
  • 88
  • 366
  • 429
  • 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
  • 4
    @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` – CTodea Jun 27 '17 at 13:48
25

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,817
  • 1
  • 17
  • 30
10

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
  • 767,688
  • 176
  • 1,542
  • 1,434
  • 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
  • 670
  • 6
  • 22