Print a comparison output in bash as a boolean value - bash

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

Related

How to define and process the opposite of a long list argument in bash

How to define and process the opposite of a long list argument in bash ?
In bash you can do it with Arithmetic Expansion if you only have arithmetical conditions:
#!/bin/bash
var4=10
var1=9
var3=2
var2=1
# Arithmetic Expansion:
# set condition to 1 if it's true or 0 if it's not
condition=$(( var4 < var1 || var3 > var2 ))
# != is "not equal to"
if [[ $condition != 1 ]]; then
echo "! cond"
else
echo "cond"
fi
The condition testing could also be done like this:
if ! ((condition)); then
where ! stands for not
In Bourne shell [ is a command (see with type [). Actually it's another name for the test command.
Let's take your example:
if [ "$var4" -lt "$var1" ]; then ...; fi
($ were added by me since those are variables)
But what if I have many conditions...
The test and [ commands accept an arbitrary list of conditions:
if [ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]; then ...; fi
(-o is a logical OR)
... and would like to take the opposite of the result of all of them like
Like any command in Bourne shell, you can test for the opposite outcome (command did not return OK) with the ! sign:
if ! [ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]; then ...; fi
Alternatively, the test and [ commands also have a NOT operator (written !):
if [ ! '(' "$var4" -lt "$var1" -o "$var3" -gt "$var2" ')' ]; then ...; fi
(you have to use parentheses here since you want to negate the whole compound expression)
Further readings with man test and help test.
Of course, you can do it in two steps like this:
[ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]
condition=$?
# Now condition contains the result of the [ command,
# which is 0 if it the test is true and 1 otherwise
if [ "$condition" -ne 0 ]; then ...
Admittedly, the [ "$condition" -ne 0 ] syntax is counter-intuitive and less readable; one may prefer Ted Lyngmo's answer instead.
Another more readable alternative:
condition=false
[ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ] && condition=true
# "true" and "false" being two Unix commands,
# we can now use condition like this:
if ! "$condition"; then ...
Since you specify bash, you can do it arithmetically -
if (( var4 < var1 )) || (( var3 > var2 )); then echo true; else echo false; fi
or all at a go --
if [[ "$var4" -lt "$var1" || "$var3" -gt "$var2" ]]; then echo true; else echo false; fi
If you use the latter, < and > evaluate their args as strings and compare them by lexigraphic sorting, which should be LOCALE specific.
c.f. https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
To get the "not" of a condition, predicate with !.
To get a compound condition as a unit you can collectively invert with that not, enclose in parens.
if [[ ! ( "$var4" -lt "$var1" || "$var3" -gt "$var2" ) ]]; then echo true; else echo false; fi
As for your question - " is there a way to do like python, defining a variable for big list of conditions and doing a not on that condition ?" - I suspect your assumption is imprecise. Python does not put all the compound conditions in the variable, it evaluates them and saves the boolean result, which the if then parses, and can negate.
It sounds like what you want to do is set up a series of tests, save the boolean, then get the reverse of that. In bash, if that's just the structure you want, I'd do something like this -
[[ ( "$var4" -lt "$var1" || "$var3" -gt "$var2" ) ]]; # set return code
if (( $? )) ... # check as math
The true return from a normal conditional is a zero (0), which evaluates to false in arithmetic context. Any other response (nonzero) evaluates as boolean false in a normal [[...]] return code, but true in arithmetic context, so evaluating $? in an arithmetic context you are implicitly applying a "not".
You can add a step between to save $? to another variable, but the result is the same. Just remember that any time you do this, you are flipping from return code testing to arithmetic testing, so it ALWAYS adds the not implicitly. That's a documentation and maintenance nightmare. I strongly recommend against it for that reason.

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

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.

Why does [ a -gt b ] (not [ "$a" -gt "$b" ]) appear to work?

I have one problem in unix shell scripting. Let me ask you with very simple example.
suppose, I am getting user input and comparing two numbers.
echo "Enter the first number"
read a
echo "Enter the second number"
read b
if [ a -gt b ]---------------------->I have not used $ before variable name.
then
echo "a is greater"
else
echo "b is greater"
fi
In this example, i should have used $ to get the value of variable. By mistake, I forgot. Still, it gives me right result, how and why?
I tried to debug by using sh -x filename. but, it doesn't show any value while comparing(because i havn't used $ sign).
How the shell decide which is greater and vice versa?
How it works internally?
Thanks.
Notably, you could use this without a $ if you did the comparison in a numeric context.
In bash:
if (( a > b )); then
echo "a is greater"
else
echo "b is greater"
fi
...would be correct, as (( )) (double parens) enters a numeric context within which all textual strings are treated as variables and automatically dereferenced (and within which operators such as > and < have their typical arithmetic meanings, rather than performing redirections).
What you're doing now is heavily implementation-dependent. [ a '>' b ] would be a lexographic comparison between the letters a and b. [ a -gt b ] is an arithmetic test.
Many shells will not allow this at all. For instance:
$ a=2; b=3; [ a -gt b ]; echo $?
-bash: [: a: integer expression expected
2
Notably, this is different inside of bash's [[ ]] test context (which doesn't carry the POSIX semantics of [ ]). There, what you're doing actually does work, though it's harder to read than the (( )) form:
$ a=2; b=3; [[ a -gt b ]]; echo $?
1
$ a=3; b=2; [[ a -gt b ]]; echo $?
0
If you're limited to POSIX sh, you don't have (( )), but you do have $(( )), which you can use almost the same way:
$ a=2; b=3; result=$(( a > b ? 1 : 0 )); echo "$result"
0
$ a=3; b=2; result=$(( a > b ? 1 : 0 )); echo "$result"
1
There is a simple explanation why your test appears to work: it blindly executes the else clause.
The [ ... ] built-in has no way to report the error to the caller except by exiting with a non-zero exit status. Incidentally, a non-zero exit status is also used to indicate "false". This means that if cannot distinguish between a false and an erroneous evaluation of its condition, which is why your comparison always prefers the else branch and thus appears to work. You can easily test it by reverting the arguments:
$ if [ b -gt a ]
then
echo "b is greater"
else
echo "a is greater"
fi
dash: 11: [: Illegal number: b
a is greater
Note that the error message printed to standard error is, as far as the if statement is concerned, a mere side effect of the test command, and is entirely ignored.

Resources