11

I've been Bash scripting for a while and I'm wondering if there's any difference between these two forms of negation with the test command:

if [ ! condition ]; then
fi

if ! [ condition ]; then
fi

The first tells the shell to pass the arguments ! condition to test, letting the program take care of the negation itself. On the other hand, the second passes condition to test and lets the shell itself negate the error code.

Are there any pitfalls I should be aware of when choosing between these two forms? What values of $condition could make the results differ between them?

(I seem to remember reading an article a while ago discussing this, but I don't remember how to find it/exactly what was discussed.)

Ken White
  • 123,280
  • 14
  • 225
  • 444
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    Not really a pitfall, since it's hard to construct a realistic scenario where this would happen, but keep in mind that in `! [ condition ]` the `!` is a shell operator, negating the exit status of a command, while in `[ ! condition ]` the `!` is just a string argument to the `[` command. After `foo="! bar"`, compare `[ "$foo" ]` with `[ $foo ]`. – chepner Mar 26 '16 at 19:35

3 Answers3

8

To build on chepner's insightful comment on the question:

  • In [ ! condition ], the ! is just an argument to the [ builtin (an effective alias of the test builtin); it so happens that [ / test interprets argument ! as negation.

  • In ! [ condition ], the ! is a shell keyword that negates whatever command it is followed by (which happens to be [ in this case).

One thing that the ! [ condition ] syntax implicitly gives you is that negation applies to whatever [ condition ] evaluates to as a whole, so if that is the intent, you needn't worry about operator precedence.

Performance-wise, which syntax you choose probably doesn't make much of a difference; quick tests suggest:

  • If condition is literally the same in both cases, passing the ! as an argument to [ is negligibly faster.

  • If ! is used as a keyword, and you are therefore able to simplify the condition by not worrying about precedence, it may be slightly faster (e.g, ! [ 0 -o 1 ] vs. [ ! \( 0 -o 1 \) ]; note that the POSIX spec. discourages use of -a and -o due to ambiguity).

That said, if we're talking about Bash, then you should consider using [[ instead of [, because [[ is a shell keyword that parses the enclosed expression in a special context that:

  • offers more features
  • allows you to safely combine expressions with && and ||
  • comes with fewer surprises
  • is also slightly faster (though that will rarely matter in pratice)

See this answer of mine.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
2

! negates the exit code of following command :

$ ! test 1 = 1 || echo $?  # negate command with true expression
1

As said in man page, test (and similar [ builtin) exit with the status determined by following expression :

$ test ! 1 = 1 || echo $?  # return false expression
1
$ [ ! 1 = 1 ] || echo $?
1

For a given expression :

  • on one side you negate the test command that exit with true expression status.
  • on the other side, you negate an expression and the test command (or [) exit with its false status

Both will have the same effect.

So I would say that these syntax are equivalent. With the advantage for external ! to allow negate compound tests :

$ ! [ 1 = 1 -a 2 = 2 ] || echo $?
1
SLePort
  • 15,211
  • 3
  • 34
  • 44
  • You might care to note that a minor adaptation of the third example in the first set also reports `1`: `test ! false || echo $?`. That's because the test is checking whether the string (`true` or `false`) is empty or not. Similarly with the second example in the first set; replacing `true` by `false` does not change the result. – Jonathan Leffler Mar 26 '16 at 18:22
  • You're right. Replaced it with what i should be : an expression. – SLePort Mar 26 '16 at 18:34
  • Maybe you should note that `==` works on `bash` but not `dash`. –  Mar 26 '16 at 21:01
  • Updated with POSIX syntax. – SLePort Mar 26 '16 at 21:28
0

There is a difference if test/[ faces an error. Consider:

x=abc
if   [ ! "$x" -gt 0 ]; then echo "first true"; fi
if ! [   "$x" -gt 0 ]; then echo "second true"; fi

The output is:

bash: [: abc: integer expression expected
second true

Unlike [[ .. ]], test/[ works like regular utility and signals errors by returning 2. That's a falsy value, and with ! outside the brackets, the shell inverts it just the same as a regular negative result from the test would be inverted.

With [[ .. ]] the behaviour is different, in that a syntax error in the condition is a syntax error for the shell, and the shell itself exits with an error:

if [[ a b ]]; then echo true; else echo false; fi

prints only

bash: conditional binary operator expected
bash: syntax error near `b'

with no output from the echos.

On the other hand, arithmetic tests work differently within [[ .. ]] so [[ "$x" -gt 0 ]] would never give an error.

ilkkachu
  • 6,221
  • 16
  • 30