-1

I read here that double parenthesis allows for C-style variable manipulation. However, it did not work as expected when I tried to compare strings:

(( "a" == "b" )) && echo yes || echo no
# shows yes

I am also confused as to how this works for using variables as booleans. Following the example in the linked answer, I tried the following:

true=1
false=0
a=true
(( a )) && echo yes || echo no
# shows yes
a=false
(( a )) && echo yes || echo no
# shows no

But wouldn't a be a string value of either true or false?

In addition, since a "no error" value (0) is taken as true and any "error" value (non-zero) is taken as false in bash, why does it look like the opposite convention is taken here?

Cyrus
  • 84,225
  • 14
  • 89
  • 153
dennis97519
  • 128
  • 8
  • 1
    *However, it did not work as expected when I tried to compare strings* What else were you expecting to happen? `((` compound command is for evaluating arithmetic expressions. What TLDP says doesn't matter at all as it's an outdated, unreliable source of information. – oguz ismail Aug 15 '20 at 06:42
  • 2
    "C-style variable manipulation" is a bad way to think about what `(( ))` does. It does *integer arithmetic*. It happens to use somewhat C-like syntax, but it's not the same as C in important ways, and it doesn't do anything except integers. If you try to do string comparisons in it (as you're doing), it'll do its best to convert those into integers (in ways you might not expect), and them compare those integers. – Gordon Davisson Aug 15 '20 at 07:08
  • @oguz TLDP is the first thing that came up on Google and I (or most casual users) don't have any idea whether something is outdated and unreliable. I was confused by the behavior and did further research and wrote test snippets to understand the behavior, so I wrote this Q&A to take note of what I found out. – dennis97519 Aug 16 '20 at 04:16

1 Answers1

2

The main thing to take note of is that the double-parenthesis construct allows for arithmetic evaluation and expansion, and is not an inline C interpreter. Thus, only the rules defined in Shell Arithmetic applies, that is, only C operations on integer types work in the double parentheses.


First example: bash constructs expand first

Anything outside the arithmetic operators are expanded first according to bash rules, e.g. quotes, parameter expansion, bash range {1..5} and list{a,b} constructs, before the double parentheses evaluation starts.

In the first example, double quotes cause what's inside to be interpreted as a single word (no effect inside double-paren) and also evaluate things starting with $ (but there's none inside the quotes), so the first example simply becomes (( a == b )).

Thus, the best way to understand how (( )) works is to work out all the bash constructs first in your mind, then plugging it in. You can also write examples to test your assumptions.

Example with parameter expansion taking place:

a=1
b=2
(( a = $b )) # becomes (( a = 2 ))
(( a = b )) # straight arithmetic evaluation of a = b within the double parenthesis
# they produce the same result but how they arrive at the result is different
(( $a = b )) # becomes (( 2 = b ))
# syntax error as that's not a valid expression.

Notes

There are some peculiarities when you compare the closely related $(( )) and (( )) constructs. The former (Arithmetic Expansion) treats the expression as if the expression is within double quotes, while the latter does not, as explained above.


Second example: Variables in the rvalue position expand recursively

There are some subtle rules in Shell Arithmetic:

  1. "The value of a variable is evaluated as an arithmetic expression when it is referenced"
  2. "or when a variable which has been given the integer attribute using declare -i is assigned a value".
  3. "A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syntax"

After trying it out for a bit, you will see that this basically means that any variables in the rvalue will be evaluated recursively until it reaches a value that is either an integer, or is an undefined/null variable name:

b=c
c=d
(( a = b ))
echo $a 
# gives 0
d=3
(( a = b ))
echo $a
# gives 3

unset d
declare -i a
a=b
echo $a
# gives 0
d=3
a=b
echo $a
# gives 3

You can also play tricks with putting expressions in variables and evaluating it later:

b=2
c=3
d=c
e=b+d
(( a = e ))
echo $a
# gives 5, as it unfolds like a=b+d; a=2+c; a=2+3

So in the example in the question, a evaluated to true, then evaluated to 1 to give the final result.


How does (( )) reverse the interpretation for true and false

(( 0 ))
echo $? # print the return code of the previous call
# prints 1, which indicates error/false in shell

(( 1 ))
echo $?
# prints 0, indicating success/true

(( 2 ))
echo $?
# prints 0

(( -1 ))
echo $?
# prints 0

So the behaviour inside the parentheses is consistent with the C interpretation for true and false, with 0 indicating false and non-zero indicating true. (( )) "converts" false to a return value of 1 and true to a return value of 0.

dennis97519
  • 128
  • 8