286

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

5 Answers 5

330

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

4
  • 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! Commented Dec 28, 2010 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: i.imgur.com/AQWuFtf.png
    – Artfaith
    Commented Apr 8, 2021 at 0:24
  • intelliJ gives this warning; You are missing a required space after the !. See SC1035. Commented Sep 7, 2022 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
    Commented Sep 8, 2022 at 22:59
196

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.

3
  • 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. Commented Dec 28, 2010 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
    Commented Jan 1, 2011 at 19:53
  • I find if [[ ! $foo =~ bar ]] safer than if ! [[ $foo =~ bar ]], because it makes easier to introduce more conditions to the if Commented Jun 27, 2017 at 13:48
29

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.

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)

3
  • 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). Commented Dec 28, 2010 at 0: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? Commented Dec 28, 2010 at 17:13
  • I don't think this will work reliably in all cases. Regex matching in superior IMHO Commented Aug 1, 2015 at 1:04
7

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

TEMP=/mnt/silo/bin
[[ ${PATH} =~ ${TEMP} ]] || PATH=$PATH:$TEMP

Not the answer you're looking for? Browse other questions tagged or ask your own question.