Bash difference or preferred negation statement
! [[ expression ]]
This will be true if the entire test returns false
[[ ! expression ]]
This will be true if the individual expression returns false.
Since the test can be a compound expression this can be very different. For example:
$ [[ ! 0 -eq 1 || 1 -eq 1 ]] && echo yes || echo no
yes
$ ! [[ 0 -eq 1 || 1 -eq 1 ]] && echo yes || echo no
no
Personally I prefer to use the negation inside the test construct where applicable but it's perfectly fine outside depending on your intent.
A ! cmd
is actually a pipe, and the !
negates the exit code of the whole pipe.
From man bash:
Pipelines A pipeline is a sequence of one or more commands separated by one of the control operators | or |&. The format for a pipeline is:
[time [-p]] [ ! ] command [ [|||&] command2 ... ]
...
If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.
In that sense, a:
! [[ 2 -eq 2 ]]
Is the negation (!
) of the exit code of the whole [[
(once all of it has been executed). Thus, this has an exit code of 0:
! [[ 2 -eq 2 && 3 -eq 4 ]]
However
A [[ ! ]]
is a Compound command (not a pipe). From man bash:
Compound Commands
...
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the conditional expression expression.
Expressions are composed of the primaries described below under CONDITIONAL EXPRESSIONS.
Therefore, this has an exit code of 1:
[[ ! 2 -eq 2 && 3 -eq 4 ]]
Because that is actually equivalent to this:
[[ ( ! 2 -eq 2 ) && ( 3 -eq 4 ) ]]
Which, by short-circuit evaluation, the right side of the &&
is never evaluated and is actually equivalent to this much shorter expression:
[[ ! 2 -eq 2 ]]
Which will always be false (1).
In short: inside [[
, the ! affects parts of the expression.
Preferred
Using the !
inside [[
is much more common (and portable to more shells (with [
)), but may be sometimes confusing if not explicitly controlled by judicious use of ()
to delimit the extent of the negation (which might become a problem inside [
constructs).
But (in bash, ksh and zsh) the result of ! [[
is pretty clearly defined and easier to understand: execute the whole [[
and negate the result.
Which one should be used is a matter of personal preference (and understanding).