Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

linux

bash

[[ " 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
like image 367
László Szilágyi Avatar asked May 19 '20 11:05

László Szilágyi


People also ask

Can I CD in a bash script?

Trying to use cd inside the shell script does not work because the shell script runs in the subshell and once the script is over it returns to the parent shell, which is why the current directory does not change.

Is bash space sensitive?

Bash indenting is very sensitive to characters. For example a space behind “do” in while/for loops will throw it of. When you have nested loops this is very ugly, and makes it hard to follow the code.

What does != Mean in bash?

The origin of != is the C family of programming languages, in which the exclamation point generally means "not". In bash, a ! at the start of a command will invert the exit status of the command, turning nonzero values to zero and zeroes to one.

What does bash flag do?

flag is the iterator variable here. In bash the do followed by while statement specifies starting of block which contains satement to be executed by while . The ending of block is specified by done .


2 Answers

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 [[..]].

like image 153
Inian Avatar answered Oct 17 '22 06:10

Inian


(( 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
like image 7
chepner Avatar answered Oct 17 '22 07:10

chepner