Why is [[ ! ! expr ]] equivalent to [[ ! expr ]] in bash?

Viewed 104

In bash, I don't understand why the 3rd command isn't true:

[[ 1 -eq 1 ]]         # $? is 0
[[ ! 1 -eq 1 ]]       # $? is 1
[[ ! ! 1 -eq 1 ]]     # $? is 1 (??)
[[ ! ( ! 1 -eq 1 ) ]] # $? is 0

It seems to do the same thing if I replace 1 -eq 1 with any true expression, and negate with any false expression.

2 Answers

[[ is an extended syntax that provides (mostly) a superset of [. To understand its behavior, then, one should start from the standard for [.

The POSIX test specification describes how [ is expected to behave. In one place, it does provide a description in line with the expectations described in this question:

! expression - True if expression is false. False if expression is true.

...but later on, the more detailed description of how parsing occurs based on number of primitives contradicts this expectation:

  • 0 arguments: Exit false (1).
  • 1 argument: Exit true (0) if $1 is not null; otherwise, exit false.
  • 2 arguments: If $1 is !, exit true if $2 is null, false if $2 is not null. If $1 is a unary primary, exit true if the unary test is true, false if the unary test is false. Otherwise, produce unspecified results.
  • 3 arguments: If $2 is a binary primary, perform the binary test of $1 and $3. If $1 is '!', negate the two-argument test of $2 and $3. (Obsolescent XSI behavior: If $1 is '(' and $3 is ')', perform the unary test of $2.. On systems that do not support the XSI option, the results are unspecified if $1 is '(' and $3 is ')'. Otherwise, produce unspecified results.
  • 4 arguments: If $1 is '!', negate the three-argument test of $2, $3, and $4. (Obsolescent XSI behavior: If $1 is '(' and $4 is ')', perform the two-argument test of $2 and $3.) On systems that do not support the XSI option, the results are unspecified if $1 is '(' and $4 is ')'. Otherwise, the results are unspecified.
  • More than 4 arguments: The results are unspecified.

In the case of ! ! 1 -eq 1, you have a five-argument case. The results are unspecified, as the standard does not specify that a five-argument case is the negation of a four-argument case if the first argument is !.


As suggested by Zilog80: If you don't want to be subject to these restrictions, consider putting your ! outside the test syntax; ! [[ ... ]] happens at a the shell command parsing layer instead of in bespoke test-syntax-specific logic, and ! ! [[ ... ]] is perfectly valid there.

When parsing !, bash sets a flag to the following expression to tell it to invert its result. Because of this, parsing two or more ! is equivalent to parsing a single !. I proposed a patch to fix this, and it got accepted.

Related