IF statement always evaluates to true [duplicate] - bash

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

Related

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

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

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

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.

Why doesn't my comparison in BASH work?

I am trying to compare some characters in BASH
read a
if (($a == "Y"))
then
echo "YES"
elif (($a == "y"))
then
echo "YES"
else
echo "NO"
fi
but for some reason it does not work as expected. It always output YES. Looking at this super simple script, I can not understand what is wrong.
It's due to spacing and the brackets.
read a
if [[ $a == "Y" ]]
then
echo "YES"
elif [[ $a == "y" ]]
then
echo "YES"
else
echo "NO"
fi
You should review bash comparison. You're trying to use an arithmetic expansion construct to do string comparison. Instead, you should use the [ exp ] or [[ exp ]] constructs.
((expression)) is used for 'arithmetic evaluation' and strings inside (( )) will be treated as variable names, thus
if (($a == "Y"))
is equivalent to
if [ $a == $Y ]
(if $Y is a string, then bash will try to expand the name until it finds a numeric value or undefined variable)
You need to use [ ] or [[ ]] to compare strings as #KRUKUSA said.

Shell equality operators (=, ==, -eq)

What is the difference between =, == and -eq in shell scripting?
Is there any difference between the following?
[ $a = $b ]
[ $a == $b ]
[ $a -eq $b ]
Is it simply that = and == are only used when the variables contain numbers?
= and == are for string comparisons
-eq is for numeric comparisons
-eq is in the same family as -lt, -le, -gt, -ge, and -ne
== is specific to bash (not present in sh (Bourne shell), ...). Using POSIX = is preferred for compatibility. In bash the two are equivalent, and in sh = is the only one that will work.
$ a=foo
$ [ "$a" = foo ]; echo "$?" # POSIX sh
0
$ [ "$a" == foo ]; echo "$?" # bash-specific
0
$ [ "$a" -eq foo ]; echo "$?" # wrong
-bash: [: foo: integer expression expected
2
(Note: make sure to quote the variable expansions. Do not leave out the double-quotes above.)
If you're writing a #!/bin/bash script then I recommend using [[ instead. The double square-brackets [[...]] form has more features, a more natural syntax, and fewer gotchas that will trip you up. For example, double quotes are no longer required around $a:
$ [[ $a == foo ]]; echo "$?" # bash-specific
0
See also:
What's the difference between [ and [[ in Bash?
It depends on the Test Construct around the operator. Your options are double parentheses, double brackets, single brackets, or test.
If you use ((…)), you are testing arithmetic equality with == as in C:
$ (( 1==1 )); echo $?
0
$ (( 1==2 )); echo $?
1
(Note: 0 means true in the Unix sense and a failed test results in a non-zero number.)
Using -eq inside of double parentheses is a syntax error.
If you are using […] (or single brackets) or [[…]] (or double brackets), or test you can use one of -eq, -ne, -lt, -le, -gt, or -ge as an arithmetic comparison.
$ [ 1 -eq 1 ]; echo $?
0
$ [ 1 -eq 2 ]; echo $?
1
$ test 1 -eq 1; echo $?
0
The == inside of single or double brackets (or the test command) is one of the string comparison operators:
$ [[ "abc" == "abc" ]]; echo $?
0
$ [[ "abc" == "ABC" ]]; echo $?
1
As a string operator, = is equivalent to ==. Also, note the whitespace around = or ==: it’s required.
While you can do [[ 1 == 1 ]] or [[ $(( 1+1 )) == 2 ]] it is testing the string equality — not the arithmetic equality.
So -eq produces the result probably expected that the integer value of 1+1 is equal to 2 even though the right-hand side is a string and has a trailing space:
$ [[ $(( 1+1 )) -eq "2 " ]]; echo $?
0
While a string comparison of the same picks up the trailing space and therefore the string comparison fails:
$ [[ $(( 1+1 )) == "2 " ]]; echo $?
1
And a mistaken string comparison can produce a completely wrong answer. 10 is lexicographically less than 2, so a string comparison returns true or 0. So many are bitten by this bug:
$ [[ 10 < 2 ]]; echo $?
0
The correct test for 10 being arithmetically less than 2 is this:
$ [[ 10 -lt 2 ]]; echo $?
1
In comments, there is a question about the technical reason why using the integer -eq on strings returns true for strings that are not the same:
$ [[ "yes" -eq "no" ]]; echo $?
0
The reason is that Bash is untyped. The -eq causes the strings to be interpreted as integers if possible including base conversion:
$ [[ "0x10" -eq 16 ]]; echo $?
0
$ [[ "010" -eq 8 ]]; echo $?
0
$ [[ "100" -eq 100 ]]; echo $?
0
And 0 if Bash thinks it is just a string:
$ [[ "yes" -eq 0 ]]; echo $?
0
$ [[ "yes" -eq 1 ]]; echo $?
1
So [[ "yes" -eq "no" ]] is equivalent to [[ 0 -eq 0 ]]
Last note: Many of the Bash specific extensions to the Test Constructs are not POSIX and therefore may fail in other shells. Other shells generally do not support [[...]] and ((...)) or ==.
== is a bash-specific alias for = and it performs a string (lexical) comparison instead of a numeric comparison. eq being a numeric comparison of course.
Finally, I usually prefer to use the form if [ "$a" == "$b" ]
Several answers show dangerous examples. The OP's example, [ $a == $b ], specifically used unquoted variable substitution (as of the October 2017 edit). For [...] that is safe for string equality.
But if you're going to enumerate alternatives like [[...]], you must inform also that the right-hand-side must be quoted. If not quoted, it is a pattern match! (From the Bash man page: "Any part of the pattern may be quoted to force it to be matched as a string.").
Here in Bash, the two statements yielding "yes" are pattern matching, other three are string equality:
$ rht="A*"
$ lft="AB"
$ [ $lft = $rht ] && echo yes
$ [ $lft == $rht ] && echo yes
$ [[ $lft = $rht ]] && echo yes
yes
$ [[ $lft == $rht ]] && echo yes
yes
$ [[ $lft == "$rht" ]] && echo yes
$

Resources