bash operators in if-statement - bash

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.

Related

Nice way of assigning true or false to value in bash [duplicate]

Can someone explain how to perform Boolean operations and store them in variables in Bash?
I tried:
A=true
B=false
C=!$A
D=$A && $B
echo $C
echo $D
I also tried without dollars, with [], with {}, ()... How can one do such a simple operation in bash?
result in console are:
!true
true
It seems they are always treated as strings.
You deduced right, bash variables by default contain strings, and its values are treated as strings.
You can use the declare built-in command to explicitly say they store integers (declare -i myintvar), or indexed arrays (declare -a myarr), or associative arrays (declare -A mymap), etc., but not booleans.
The closest you can get to booleans is to use integer values 0 and 1 and evaluate expressions as arithmetic expressions with the (( expr )) command (bash-specific), or with arithmetic expansion $(( expr )) (POSIX-compatible). Those commands evaluate expr according to rules of shell arithmetic.
For example:
A=1
B=0
(( C = \!A )) # logical negation ==> C = 0
(( D = A && B )) # logical AND ==> D = 0
E=$(( A ^ B )) # bitwise XOR ==> E = 1
In bash, you can also use declare -i and let:
declare -i E='A||B' # equivalent to: E=$((A||B)), or ((E=A||B))
let C='!A' # equivalent to: C=$((\!A)), or ((C=\!A))
which are a longer way of saying ((..)) or $((..)). They both force arithmetic evaluation of the expressions given.
Note that ! has a special meaning in most shells (including bash), it causes history expansion. To prevent it, we must escape it with a backslash, or quote it.
Unfortunately, bash does not support boolean variables in a proper meaning. There is no "true" and "false" constants as in programming languages. Instead, /bin/true and /bin/false are two executables that don't do anything except return exit status 0 or 1. Contrary to common logic, exit status 0 is a synonim for "true" and 1 is a synonim for "false". The closest you can get to evaluating boolean expressions is either
[[ expr ]] which returns a exit status 0 or 1 depending on evaluating expr
&& and || (these are conditionals depending on last command's exit status)
[ which is actually an executable with a silly name (not part of bash) that supports some basic expressions
if...elif..else..fi which you can use to your advantage to manipulate variables within the workflow
like this?
t=true
f=false
# if $t; then echo Hi; fi
Hi
# if $f; then echo Hi; fi
# if ! $f; then echo Hi; fi
Hi
# if ! ($t && $t); then echo Hi; fi
# if ($t && $t); then echo Hi; fi
Hi

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.

How does the double parenthesis construct work in bash?

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?
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:
"The value of a variable is evaluated as an arithmetic expression when it is referenced"
"or when a variable which has been given the integer attribute using declare -i is assigned a value".
"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.

Using variables in Bash boolean expressions

Can someone explain how to perform Boolean operations and store them in variables in Bash?
I tried:
A=true
B=false
C=!$A
D=$A && $B
echo $C
echo $D
I also tried without dollars, with [], with {}, ()... How can one do such a simple operation in bash?
result in console are:
!true
true
It seems they are always treated as strings.
You deduced right, bash variables by default contain strings, and its values are treated as strings.
You can use the declare built-in command to explicitly say they store integers (declare -i myintvar), or indexed arrays (declare -a myarr), or associative arrays (declare -A mymap), etc., but not booleans.
The closest you can get to booleans is to use integer values 0 and 1 and evaluate expressions as arithmetic expressions with the (( expr )) command (bash-specific), or with arithmetic expansion $(( expr )) (POSIX-compatible). Those commands evaluate expr according to rules of shell arithmetic.
For example:
A=1
B=0
(( C = \!A )) # logical negation ==> C = 0
(( D = A && B )) # logical AND ==> D = 0
E=$(( A ^ B )) # bitwise XOR ==> E = 1
In bash, you can also use declare -i and let:
declare -i E='A||B' # equivalent to: E=$((A||B)), or ((E=A||B))
let C='!A' # equivalent to: C=$((\!A)), or ((C=\!A))
which are a longer way of saying ((..)) or $((..)). They both force arithmetic evaluation of the expressions given.
Note that ! has a special meaning in most shells (including bash), it causes history expansion. To prevent it, we must escape it with a backslash, or quote it.
Unfortunately, bash does not support boolean variables in a proper meaning. There is no "true" and "false" constants as in programming languages. Instead, /bin/true and /bin/false are two executables that don't do anything except return exit status 0 or 1. Contrary to common logic, exit status 0 is a synonim for "true" and 1 is a synonim for "false". The closest you can get to evaluating boolean expressions is either
[[ expr ]] which returns a exit status 0 or 1 depending on evaluating expr
&& and || (these are conditionals depending on last command's exit status)
[ which is actually an executable with a silly name (not part of bash) that supports some basic expressions
if...elif..else..fi which you can use to your advantage to manipulate variables within the workflow
like this?
t=true
f=false
# if $t; then echo Hi; fi
Hi
# if $f; then echo Hi; fi
# if ! $f; then echo Hi; fi
Hi
# if ! ($t && $t); then echo Hi; fi
# if ($t && $t); then echo Hi; fi
Hi

Bash exit status of shorthand increment notation

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.

Resources