Bash exit status of shorthand increment notation - bash

I noticed an apparent inconsistency in the return status of bash's (( )) notation.
Consider the following
$> A=0
$> ((A=A+1))
$> echo $? $A
0 1
However using the other well known shorthand increment notation yields:
$> A=0
$> ((A++))
$> echo $? $A
1 1
If one has the builtin set -e in the script the second notation will cause the script to exit, since the exit status of the ((A++)) returned non-zero. This question was more or less addressed in this related question. But it does not seem to explain the difference in exit status for the two notations ((A=A+1)) and ((A++))
((A++)) seems to return 1 if and only if A equals 0. (Disclaimer: I have not done exhaustive tests. Tested in bash 4.1.2 and 4.2.25). So the final question boils down to:
Why does A=0; ((A++)) return 1?

a++ is post-increment: it increments after the statement is evaluated. By contrast, ++a increments before. Thus:
$ a=0 ; ((a++)) ; echo $a $?
1 1
$ a=0 ; ((++a)) ; echo $a $?
1 0
In the first case, ((a++)), the arithmetic expression is evaluated first, while a is still zero, yielding a value of zero (and hence a nonzero return status). Then, afterward, a is incremented.
In second case, ((++a)), a is incremented to 1 and then ((...)) is evaluated. Since a is nonzero when the arithmetic expression is evaluated, the return status is zero.
From man bash:
id++ id--
variable post-increment and post-decrement
++id --id
variable pre-increment and pre-decrement

The exit status of the (()) notation is zero if the arithmetic expression is nonzero, and vice versa.
A=A+1
You assign 1 to A, so the expression evaluates to 1, exit status zero.
A++
POST-increment operator. The expression evaluates to zero, exit status 1, and then A is incremented.

Related

bash operators in if-statement

Im new to bash and can't understand how the operator quite work here.
The goal is to creating a script which ask user to input "y" to run the command or "n" to not run.
by using ==:
All inputs get passed such as (y, n, a, abc, etc..)
if (($TFENGINE_VAR==y))
by using =:
All inputs get rejected such as (y, n, a, abc, etc..)
if (($TFENGINE_VAR=y))
echo "run tfengine? (y/n)"
read TFENGINE_VAR
if (($TFENGINE_VAR==y))
then
echo "running tfengine command.."
sleep 1
tfengine --config_path=main.hcl --output_path=terraform/ -delete_unmanaged_files
else
echo "continue.."
continue
fi
Expressions surrounded with (( )) are evaluated as numeric expressions.
You need to use the test command, which returns 0 if test is successful, and a number different from 0 if not.
There are several options to the test command. In your case, you want to compare strings, so use each parameter surrounded with double quotes.
Also, when comparing strings you don't use == as the comparison operator.
if test "$TFENGINE_VAR" = "y"
then
...
Take a look at test man page: http://man.he.net/?topic=test&section=all
Your question is:
how the operator quite work here
The ((...)) is an arithmetic expression. Inside, all strings are interpreted as variables, and undefined variables are interpreted as equal 0. The exit status of ((...)) is non-zero if the expression is equal to zero, and the exit status is zero if the expression inside is equal to non-zero.
Because y variable is not defined, y is equal to 0. The (( $TFENGINE_VAR == y )) does the same as (( $TFENGINE_VAR == 0 )).
If you want to compare strings, you would use [[ or [ or test command.
All inputs get rejected such as
Sure, = is assignment inside arithmetic expression. If you input abc, then (( $TFENGINE_VAR = y )) becomes (( abc = 0 )) and it assigns the value 0 to variable abc. Note that variable expansion happens before the expansion of variables inside ((.
$ TFENGINE_VAR=abc
$ (($TFENGINE_VAR=y))
$ echo $abc
0
$ y=12345
$ (($TFENGINE_VAR=y))
$ echo $abc
12345
The result of assignment is equal to the value assigned, 0 in this case, so ((...)) exits with non-zero exit status, failure, so if branches to else part.

Why does bash post-increment operator set a nonzero result code? [duplicate]

This question already has answers here:
Bash exit status of shorthand increment notation
(2 answers)
Why does ((count++)) return 1 exit code first time run
(1 answer)
Closed 1 year ago.
This caught me by surprise: applying the post-increment operator
(++) to a zero value variable results in a non-zero result code.
That is, given:
#!/bin/bash
myvar=0
let myvar++
echo "result: $?"
Running that (with bash 5.1.0) results in:
result: 1
Why does that produce a nonzero result code? We see the same behavior
using a numeric expression:
#!/bin/bash
myvar=0
(( myvar++ ))
echo "result: $?"
On the other hand, if we use += instead of ++, or if we start with
a nonzero value of myvar, we receive a 0 result code as expected. The
following...
myvar=1
let myvar+=1
echo "result: $?"
myvar=1
let myvar++
echo "result: $?"
myvar=1
(( myvar++ ))
echo "result: $?"
...all produce:
result: 0
What's going on here?
From the bash man page, in the section describing let:
Each arg is an arithmetic expression to be evaluated (see ARITHMETIC EVALUATION). If the last arg evaluates to 0, let returns 1; 0 is returned otherwise
It's not the operator that establishes the value of $?, it is let. Since the value of the argument to let in the command let myvar++ is 0, let returns 1.
For post-increment expressions, bash evaluates the variable and sets the result code before applying the increment. In each case where the value before the post-increment was 0, it follows the documented behavior,
If the last arg evaluates to 0, let returns 1; 0 is returned otherwise.
Note the difference if you use pre-increment,
myvar=0
let ++myvar
echo "result: $?"
>> result: 0
In exp0() in https://git.savannah.gnu.org/cgit/bash.git/tree/expr.c#n1014 , for PREINC it binds the (stringifed) value of v2 then assigns it to val, but under POSTINC it binds the variable then discards v2 without assigning it to val.

Bash errexit with arithmetic expansion [duplicate]

This question already has an answer here:
Why does ((count++)) return 1 exit code first time run
(1 answer)
Closed 5 years ago.
I have the following Bash script called errexit:
#! /bin/bash
set -ex
a=$1
((a++))
echo $a
When I run it with the argument "0", it triggers the error exit:
$ ./errexit 0; echo $?
+ a=0
+ (( a++ ))
1
But when I run it with the argument "1", it does not trigger the error exit:
$ ./errexit 1; echo $?
+ a=1
+ (( a++ ))
+ echo 2
2
0
I do not understand, why 2 is not interpreted as false, although it is the case in an if statement:
$ if grep nix nix; then echo $?=t; else echo $?=f; fi
grep: nix: No such file or directory
2=f
Can anybody explain why the arithmetic expansion behaves differently?
According to the Bash manual:
((expression))
The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to let "expression".
So according to the post-operator. The value of ((a++)) when a=0 is 0. So the return status is 1.
a=0
echo $((a++)) # 0

Why do bash calculations return 1 if the result is zero

I'm doing simple calculations in bash and I noticed that bash calculations using expr return 1 if the result of the calculation is zero.
user#machine:~$ expr 42 - 40 ; echo $?
2
0
user#machine:~$ expr 42 - 84 ; echo $?
-42
0
user#machine:~$ expr 42 - 42 ; echo $?
0
1
Why does bash do this? Isn't a return value of 1 an indication that the program failed? Doesn't this behaviour violate good practice?
I'm doing some calculations inside a script run with set -e.
What is the best way to account for the return value of 1?
I could do
expr $var1 - $var2 || true
but this would also ignore other exit codes than 1.
This is just the mapping of integers (as Boolean values, 0=False, 1=True) to shell exit codes (0=Success, >0=Failure). Note that "failure" does not necessarily mean "program experienced an error and/or crashed". Other programs do similar things:
* `grep` exits with 0 if a match is made, 1 if no match is made.
* `test` exits 0 if its condition is true, 1 if its condition is false.
In C, you can use the result of an arithmetic expression directly as the condition of an if or while statement.
In shell, such statements test the exit code of a command, treating success as analogous to true and failure as analogous to false. In this sense, expr ... is short for test ... -ne 0.
In a typical language, you write something like
# Python
n = 10
while n - 3: # short for n > 3
...
expr performs arithmetic, so it makes sense for the exit status to allow the same.
n=10
while expr "$n" - 3; do
...
done
Note that you can still detect actual errors, as opposed to Boolean falseness, by checking if the exit status of expr is greater than 1.
n=10
while true; do # "Infinite" loop
expr "$n" - 3
case $? of
1) break ;;
2) echo "Error with expr" >&2; break ;;
esac
...
done

Why b doesn't change always its value when I run ((a++)) && ((b++)) in bash?

Let's take the following example from my terminal:
$ a=0 && b=1
$ echo $a $b
0 1
$ # Everything OK
$ ((a++)) && ((b++))
$ echo $a $b
1 1
$ # What? Why only a changed its value and b no?
$ ((a++)) && ((b++))
$ echo $a $b
2 2
$ # Now the value of b has changed...
Can someone to make me understand why this is happening?
a++ is post-increment, i.e. the increment happens after the value is tested.
The test on a fails because a is zero at the time it is tested.
&& is the logical AND operator. If its first argument is false, it doesn't bother evaluating the second. This is called "short-circuiting" and saves processing time.
Since ++ is post increment, if a is zero when evaluating ((a++)) && ((b++)), it evaluates a first, gets zero (which is false), adds 1 to a, then quits due to the short-circuit without evaluating the second part. So b does not get incremented.
((a++)) increments the value of $a in 1. Its exit status $? is 0 if the evaluated expression was not 0 and 1 otherwise. If its exit status is, than, because of && the second command - ((b++)) - will not run.
As man bash indicates, if the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. As per a++ being a post-increment expression, the expression evaluates $a itself and then increments its value, so that the return status of ((a++)) will be 0 when the expression is non-zero, and 1 otherwise.
So, counter-intuitively:
when $a is unset, the return status will be 1.
when $a is a string, the return status will be 1.
when $a is equal to 0, the return status will be 1.
when $a is set to a number different from 0, the return status will be 0.
See an example:
The variable is 0.
$ t=0
We perform ((t++)) and see what the command is returning with the echo $( command ) expression:
$ echo $(((t++)))
0
But note that the value of $t has been incremented.
$ echo $t
1
The same if we do it again:
$ echo $(((t++)))
1
$ echo $t
2
$ echo $(((t++)))
2
$ echo $t
3
See man bash:
((expression))
The expression is evaluated according to the rules described below
under ARITHMETIC EVALUATION. If the value of the expression is
non-zero, the return status is 0; otherwise the return status is 1.
This is exactly equivalent to let "expression".
When a is 0, the value of ((a++)) is interpreted as not true.

Resources