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
or1
depending on the evaluation of the conditional expressionexpression
. 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 ifarg1
is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal toarg2
, 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