简体   繁体   中英

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

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

I frequently use the above solution to check the input range of my Bash script.

Now I realise that the extended arithmetic expression (()) looks like it is suppressed inside the double bracket [[]] .

To illustrate the problem:

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

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

The above result is false because n not less than 3 if they are treated as numbers. This is the correct solution:

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

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, ie

(( $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 eg 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM