Why doesn't Bash `(())` work inside `[[]]`?

The GNU bash man page for [[..]] explains that the operator runs a conditional expression and

Return a status of 0 or 1 depending on the evaluation of the conditional expression expression. Expressions are composed of the primaries described below in Bash Conditional Expressions.

But the arithmetic operator is not part of the supported conditional expressions (primaries) inside [[..]] which means the expression is forced to run as a string comparison, i.e.

(( $n < 3))

is not run in arithmetic context but just as plain lexicographic (string) comparison as

[[ 100 < 3 ]] 

which will always result true, because the ASCII values for 1, 0, 0 appear before 3

But inside [[..]] arithmetic operations are supported if you use -lt, -gt

arg1 OP arg2

OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively.

So had you written your expression as

a=start; n=100; [[ " stop start status " =~ " $a " && $n -lt 3 ]] && echo ok || echo bad
bad

it would have worked as expected.

Or even if you had forced the arithmetic expression usage by prefixing $ before ((..)) and written it as below (note that bash does not have documented behavior for $((..)) inside [[..]]). The likely expected behavior is the arithmetic expression is expanded before the [[..]] is evaluated and the resultant output is evaluated in a string context as [[ 0 ]] which means a non-empty string.

a=start; n=5; [[ " stop start status " =~ " $a " && $(( $n < 3 )) ]] && echo ok || echo bad

The result would still look bad, because the arithmetic expression inside [[..]] decomposes into an unary string not empty comparison expression as

$(( 5 < 3 ))
0
[[ -n 0 ]]

The result of the arithmetic evaluation 0 (false) is taken as a non-zero entity by the test operator and asserts true on the right-side of &&. The same would apply for the other case also e.g. say n=1

$(( 1 < 3 ))
1
[[ -n 1 ]]

So long story short, use the right operands for arithmetic operation inside [[..]].


(( is a "keyword" that introduces the arithmetic statement. Inside [[, however, you can't use other statements. You can use parentheses to group expressions though, so that's what (( ... )) is: a redundant "double group". The following are all equivalent, due to the precedences of < and &&:

  • [[ " stop start status " =~ " $2 " && (($#<3)) ]]
  • [[ " stop start status " =~ " $2 " && ($#<3) ]]
  • [[ " stop start status " =~ " $2 " && $#<3 ]]

If you want integer comparison, use -lt instead of <, but you also don't need to fit everything inside [[ ... ]]. You can use a conditional statement and an arithmetic statement together in a command list.

{ [[ " stop start status " =~ " $2 " ]] && (($#<3)) ; } || { echo "Usage $0 file_name command"; exit 1;}

In this case, ... && ... || ... will work the way you expect, though in general that is not the case. Prefer an if statement instead.

if [[ " stop start status " =~ " $2 " ]] && (($#<3)); then
  echo "Usage $0 file_name command"
  exit 1
fi

Tags:

Linux

Bash