if [ 0 ] returns true. why? - bash

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.

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.

Korn conditional operator not working

I wrote a small korn script, but when I try to run the script, it will not echo the message I want to display. When I try to run it by
ksh script.sh -1
it is not echoing the message.
if [ $# -le 0 ]
then
echo "That is a negative integer!"
exit
fi
In bash/ksh $# represents the number of arguments passed as parameters.
What you needed is
if [ ${1:-0} -lt 0 ] # $1 is the first parameter
then
echo "That is a negative integer!"
exit
fi
Or a shorted version of the above
[ ${1:-0} -lt 0 ] && echo "That is a negative integer!" && exit
Edit 1
I have used shell [ parameter expansion ] ${1:-0} supply a default value.
Since zero is technically not a negative number, replace -le with -lt
Edit 2
If you're looking forward to match a particular string then do below
[ ${1:-NULL} = "StringToMatch" ] && DoSomething
If you're looking to see if the output is just has atleast one non-digit character,then do something like below
[[ {1:-NULL} =~ [^[:digit:]]+ ]] && DoSomething
Warning : Not all expansions mentioned in the link may not be supported by ksh

bash if with or and negation

why does:
#!/bin/bash
wtf=false
if [ $wtf ] || [ ! -f filethatexists.whatever ]
then
echo "WTF1"
fi
if [ ! -f filethatexists.whatever ]
then
echo "WTF2"
fi
print:
WTF1
instead of nothing? It is especially perplexing that the second form works as expected and the first not.
The basic test
[ $wtf ]
tests whether the string in the middle is empty or not.
Since $wtf contains the string 'false', the test returns true, or exit status 0 for success, because 'false' is not the same as the empty string '' — and hence you get WTF1 as the response.
Try with:
wtf=''
As pointed out by Gordon Davisson (and Dennis Williamson), it is a good idea to be careful with strings that you are testing. Indeed, I should have stated that I would always use [ -n "$wtf" ] or [ -z "$wtf" ] to test whether a variable is set, because that was necessary when I was learning shell, once upon a quarter century ago. I've had counter stories from Bash afficionados that you don't have to worry about it in Bash - however, I think the code here provides a counter-example that in fact you do still have to worry about it.
So, some best practices:
Enclose tested variables in double quotes, or
(In Bash), use [[ $wtf ]] which does know how to handle the variable expansion.
Use the -n or -z tests to test for non-empty or empty values.
There can be exceptions to the rules - but you will not go far wrong following them.
Consider the code:
wtf="1 -eq 0"
[ $wtf ] && echo "WTF0"
[[ $wtf ]] && echo "WTF1"
wtf="false"
[ $wtf ] && echo "WTF2"
[[ $wtf ]] && echo "WTF3"
wtf=""
[ $wtf ] && echo "WTF4"
[[ $wtf ]] && echo "WTF5"
wtf="false"
[ "$wtf" ] && echo "WTF6"
[[ "$wtf" ]] && echo "WTF7"
wtf=""
[ "$wtf" ] && echo "WTF8"
[[ "$wtf" ]] && echo "WTF9"
That produces:
WTF1
WTF2
WTF3
WTF6
WTF7
with both bash and ksh (as found on MacOS X 10.6.4, when run with 'bash testcode.sh' or 'ksh testcode.sh'). A real Bourne shell (if you can still find such a thing) would object to the double-bracket operations - it would not be able to find the command '[[' on $PATH.
You can extend the testing to cover more cases ad nauseam.
Here's a handy little trick:
wtf=false
if $wtf || [ ! -f filethatexists.whatever ]
In this form, the contents of the variable are executed and the return value determines whether the test passes or fails. It happens that true and false are Bash builtins that return the appropriate value.
if [ $wtf = true ] || [ ! -f . .

Resources