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).