Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 440
vimene Avatar asked May 18 '21 14:05

vimene


People also ask

What is $ $ ( (...) ) used for in Bash?

$ ( (...)) is called arithmetic expansion, which is typical of the bash and ksh shells. This allows doing simple integer arithmetic, no floating point stuff though.

What is the difference between letlet and $ (...) in Bash?

let is a bash and ksh keyword which allows for variable creation with simple arithmetic evaluation. If you try to assign a string there like let a="hello world" you'll get a syntax error. Works in bash and ksh93. $ (...) is command substitution, where you literally take the output of a command and assign to a variable.

What is arithmetic expansion in Bash shell?

Some of these are specific to bash shells, while others aren't. $ ( (...)) is called arithmetic expansion, which is typical of the bash and ksh shells. This allows doing simple integer arithmetic, no floating point stuff though. The result of the expression replaces the expression, as in echo $ ( (1+1)) would become echo 2

Is it possible to assign an expression to a variable in Unix?

But that is not true. This is the correct way to implement this. The expression s+1 is evaluated by the shell and can be assigned to a variable. Here the expression will be calculated by the program expr, which isn't a shell builtin but an external Unix program.


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

like image 84
Charles Duffy Avatar answered Nov 03 '22 13:11

Charles Duffy