bash: elementary if-conditions not working? (string comparison) - bash

When I run this script in bash:
#!/bin/bash
str="no"
if [ "$str"="yes" ]; then
echo "condition 1 is true"
fi
if [ "$str"=="yes" ]; then
echo "condition 2 is true"
fi
if [[ "$str"="yes" ]]; then
echo "condition 3 is true"
fi
if [[ "$str"=="yes" ]]; then
echo "condition 4 is true"
fi
if (( "$str"="yes" )); then
echo "condition 5 is true"
fi
if (( "$str"=="yes" )); then
echo "condition 6 is true"
fi
To my surprise, I get:
condition 1 is true
condition 2 is true
condition 3 is true
condition 4 is true
condition 5 is true
condition 6 is true
Note that $str is set to no, not yes.
I don't fully understand the difference between = and == (at least, not in this bash context, I do in other languages) or between putting the if condition single [ ... ] or double [[ ... ]] or double (( ... )) brackets.
However I'm obviously something very wrong here, but I can't see it?

First and foremost, not having a spaces around the operators, you're actually testing (for instance)
[ "no=yes" ]
which will evaluate as true (non-empty string).
[...] is testing using [ external test command (presumably /bin/[) and [[...]] is shell (bash or ksh for instance) built-in test. For test = and == have the same meaning. Which in case of built-in test ([[...]]) is actually evaluated for a pattern match: i.e. [[ yeees == y*s ]] is also true.
((...)) is arithmetic evaluation. = is an assignment, and == tests equality. In my case (could come down to bash version) #5 actually yields false unless I've set yes=1 beforehand as the evaluation return value assigned... in this case new variable named no since that is what str pointed (resolved) to. For the comparison, this arithmetic comparison will return true, if values of both variables are equal... which literally means (( no == yes )) or in test syntax [[ "$no" -eq "$yes" ]]. If neither no nor yes are set, two 0s are compared.

You need to separate the equal signs with spaces. For instance:
#!/bin/bash
str="no"
if [ "$str" = "yes" ]; then
echo "condition 1 is true"
fi
...

Related

Print a comparison output in bash as a boolean value

I have a question
a=1
b=2
I want the comparison output to a variable. ie in windows languages you can write something like this. it should print false
print ($a == $b)
tries these below in console.
echo $a -eq $b
echo (( $a -eq $b ))
echo "$a" -eq "$b"
c= $(expr "$a" -eq "$b" )
echo $c
You can use arithmetic expansion.
echo $(( a == b ))
This will print 1 if the expression is true and 0 if the expression is false. No need to add $ before variable names, you can use operators like in C language and the spaces can be omitted. See Bash reference manual:Shell arithmetic for more info.
Having it to print a string "true" or "false" is a bit more tricky. Usually I go with the same as #Inian, but using if ... then .. else ... fi because I usually code under set -euo pipefail:
if (( a == b )); then echo true; else echo false; fi
but we can be smart and do an array:
to_bool[0]="false"
to_bool[1]="true"
echo ${to_bool[$(( a == b ))]}
but I see no difference then printing just 0 or 1.
I do not think it is possible to do it in bash directly.
But you can do something as the following based on the return code of the comparison operator:
res=0; [ "$s1" == "$s2" ] && res=1
echo $res
It sets res to zero first and then only if the comparison succedes sets the res variable to 1.
Alternatively, something more concise is the following:
[ $s1 -eq $s2 ]; echo $((!$?))
which literally prints the return code of the previously executed command. Note the ! not operator applied to the return code as 0 usually means success i.e. in this case the variable are the same.
Note that bash does not natively support a bool type operator. All the commands/built-ins return an exit code to the shell depending upon its success/failure status. A code 0 to the shell means the operation is success and any non-zero would represent a failure.
So in bash, you do the comparison and need to set the bool strings explicitly, something like
[[ $a == $b ]] && echo true || echo false
Note that using echo true should not confused with the built-ins /bin/true and /bin/false which explicitly set the exit code to the shell to 0 and 1 respectively. The echo statement just prints out the string mentioned to the standard output.
Also note that [[ is a shell keyword extension to the POSIX-ly available [ construct. The former is a added to the bourne again shell and may not available in other POSIX compliant shells. If you are looking for a portable way of doing this, use the [ with the case construct as
[ "$a" -eq "$b" ]
case $? in
0) printf '%s\n' "true" ;;
*) printf '%s\n' "false" ;;
esac

IF statement always evaluates to true [duplicate]

This question already has an answer here:
Bug or feature in Bash test operator [[ ... -eq ... ]]?
(1 answer)
Closed 4 years ago.
I have the following snippet, where the function test echoes false. I use the echoed value in an if statement with shell substitution:
#!/usr/bin/env bash
test () {
echo "false"
}
if [[ "$(test)" -eq "true" ]]
then
echo hello world
fi
I would expect to above to not print hello world, because I assume this would end up saying [[ "false" -eq "true" ]].
However when I run the script it echoes hello world.
If you read the manual for test, you would realize that the -eq, -gt, and -lt test conditions are only for numeric values.
INTEGER1 -eq INTEGER2
INTEGER1 is equal to INTEGER2
i.e., if [ 0 -gt 1 ];
For string comparison in bash, simply use an =.
Try using
if [[ "$(test)" = "true" ]]
This will give you the expected behavior.
try this
#!/usr/bin/env bash
test () {
echo "false"
}
if [[ "$(test)" = "true" ]]
then
echo hello world
fi

ge vs double equal in bash

I am trying to solve a hackerrank exercise.
If n is odd, print Weird
If n is even and in the inclusive range of 2 to 5, print Not Weird
If n is even and in the inclusive range of 6 to 20, print Weird
If n is even and greater than 20, print Not Weird
My code is as follows:
read n
if [ $n%2==0 ]; then
if [ $n -ge 6 ] && [ $n -le 20 ]; then
echo "Weird"
else
echo "Not Weird"
fi
else
echo "Weird"
fi
When I give the input as 3, the result I get is Not Weird which is not correct same for 1 I get Not Weird. However, when I try this:
read n
if [ $(($n%2)) -eq 0 ]; then
if [ $n -ge 6 ] && [ $n -le 20 ]; then
echo "Weird"
else
echo "Not Weird"
fi
else
echo "Weird"
fi
I get the right result. What is the difference?
[ ] (or test) builtin:
==, or to be POSIX compliant =, does a string comparison
-eq does a numeric comparison
Note: == and -eq (and other comparisons) are parameters to the [ command, so they must be separated by whitespace, so $n%2==0 is invalid.
[[ ]] keyword:
is as [ except that it does pattern matching. Being a keyword rather than a builtin, expansion with [[ is done earlier in the scan.
(( )) syntax
Carries out arithmetic evaluation as with the let builtin. Whitespace separators are not mandatory. Using a leading $ to expand a variable is not necessary and is not recommended since it changes the expansion order.
For truth evaluation inside if-else, bash provides ((..)) operators with no need of a $ on the front.
n=5
if (( (n % 2) == 0 )); then
echo "Something"
if (( n >= 6 )) && (( n <= 20 )); then
echo "Some other thing"
else
echo "Other else thing"
fi
else
echo "Something else"
fi
Read here for more information.

Why an unset variable gets evaluated as 0 in bash if statement

I'm trying to understand why an unset variable gets evaluated as 0.
in some scripts im writing the variable will be set only if needed and some time it does not.
so this kind of behave will result with incorrect output.
does it mean i must preset all my variables or at least add check that they are set ?
#!/bin/bash
#myvalue=0 #comment to simulate an unset variable.
if [[ $myvalue -eq 0 ]] ; then
echo "OK"
fi
result with OK:
bash -x test.sh
+ [[ '' -eq 0 ]]
+ echo OK
OK
The -eq operator inside [[ ... ]], since it only applies to integer values, triggers arithmetic evaluation of its operands. In an arithmetic expression, unset variables default to 0. A more obvious demonstration of arithmetic evaluation:
$ if [[ 3 -eq "1 + 2" ]]; then echo equal; fi
equal
Note that in your example, you don't even need to expand the parameter first; the arithmetic evaluation will do it for you:
$ if [[ myvalue -eq 0 ]]; then echo equal; fi
equal
$ myvalue=3
$ if [[ myvalue -eq 3 ]]; then echo equal; fi
equal
Also, this is specific to the bash [[ ... ]] command. With POSIX [, -eq does not trigger arithmetic evaluation.
$ if [ "$myvalue" -eq 0 ]; then echo equal; fi
bash: [: : integer expression expected
$ if [ myvalue -eq 0 ]; then echo equal; fi
bash: [: myvalue: integer expression expected
If you want the literal value to be the comparison use = instead of -eq.
if [[ $myvalue = 0 ]] ; then
echo "OK"
fi
The arithmetic binary operator (-eq) returns true if arg1 is equal to 0, which $myvalue is, whether set to 0 or not set at all... '' is null, which equals zero.

if [ 0 ] returns true. why?

I'm now learning shell-script. Anyway, I run the below program.
#!/bin/bash
sudo rmmod my_driver
make clean
make
sudo insmod my_driver.ko // return value is 0
var=$?
if [ $var ];
then
echo " $var, not done!!"
else
echo " $var, done!!"
fi
The output is,
...
make[1]: Leaving directory `/usr/src/kernels/2.6.32-431.11.2.el6.x86_64'
0, not done!!
In C language(I believe in any language), if if condition returns false it'll execute else block.
I tried below conditions too, but I faced same problem
if [ "$var" ]
if [ "$var" -eq "0" ]
if [ $var -eq 0 ]
if [ $? ]
Why it is not executing else block?
How can I execute else block for if [ 0 ]?
Passing any string to the test command, i.e. [, would succeed. So
if [ 0 ]
is equivalent to
if [ 1 ]
or
if [ foobar ]
You could make use of the arithmetic context instead, and say:
if ((var)); then
or, using the test command as:
if [ $var -eq 0 ]; then
If you want it to execute the else block if 0, then you have to set it so the condition is anything but 0.
As variables in bash are considered a string, using arithmetic operators (-ne, -gt, -eq) etc, will make it interperet it as a number.
In C you would have to set this as an int/bool for it to use equate to false when 0.
if [[ $var -ne 0 ]];then
echo " $var, not done!!"
else
echo " $var, done!!"
fi
please try [[ test statement,
atleast following will work:
if [[ "$var" -eq "0" ]]
if [[ $var -eq 0 ]]
Why other would not work, well thats because
anything like :
if [ $? ] or if [ "$var" ]
is considered as true in bash
For difference between the [ and [[
In C language(I believe in any language) : there's your problem.
I find it easier to consider that if does not test for true or false but for success or failure. That makes it easier to reconcile the effect of zero, since zero is defined as success.
In Bash, true and : are built-ins (not constants or literals) and both give success.
If you use the test command [ ], (or even [[ ]]) then values resolve to strings:
echo "hello"
if [ $? ]
then
echo "True"
else
echo "False"
fi
Gives True.
However, if you use the arithmetic test (( )), then zero is "false":
echo "hello"
if (( $? ))
then
echo "True"
else
echo "False"
fi
Gives False
If you think about it, that is the same behaviour as C, where '0' (a const char) will give true but 0 (an int) will give false. That's why if you want a binary zero char in C you use '\0'.
A language where zero (string or numeric) acts as true is Ruby (only nil and false are not true).
Try this,
[[ $var -ne 0 ]] && echo "$var, not done!!" || echo "$var, done!!"
I suggest you run man test. if does nothing but check the return command of the subsequent command, and [ is an alias of test.
Looking at the man page for test you'll find that if the expression you feed it is just a single string it defaults to testing if the string is of non-zero length.
If you want to test for the test for the numeric value of zero, the you must use [ $? -eq 0 ]
It's also worth noting that bash is not C, especially in that a return code of zero is construed as true and non-zero values are false.

Resources