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.
Related
The following code (tested with bash, zsh, and ksh, results may vary for other shells) returns a is 0 (num). The same happens for a=''. Results for assigning a=0 or a=1 are predictable. Quoting expressions does not change the result. So, why do double brackets treat null and empty variables as being numerically equal to zero?
unset a
#a=''
#a=0
#a=1
if [[ $a == 1 ]] ; then
echo 'a is 1 (string)'
fi
if [[ "$a" == 0 ]] ; then
echo 'a is 0 (string)'
fi
if [[ $a -eq 1 ]] ; then
echo 'a is 1 (num)'
fi
if [[ "$a" -eq 0 ]] ; then
echo 'a is 0 (num)'
fi
I'm deliberately avoiding the broader issues of single and double brackets, since it's very well covered other places on this site and elsewhere. Surprisingly, I've been unable to find anything that documents this particular behavior.
Further evidence:
unset a ; if [[ $a -gt -1 ]] ; then echo 'a > -1' ; fi
unset a ; if [[ $a -lt 1 ]] ; then echo 'a < 1' ; fi
I don't believe it is explicitly documented, but it's the -eq operator that forces a quasi-arithmetic context for its operands. Consider:
$ [[ "(3 + 5)" -eq 8 ]] && echo qed
qed
The behavior for variables is documented under ARITHMETIC EVALUATION:
A shell variable that is null or unset evaluates to
0 when referenced by name without using the parameter expansion syntax.
though it's not obvious that this should also apply to a string that results from a parameter expansion, and indeed the behavior is different in an arithmetic expression or arithmetic command itself:
$ unset a
$ (( a == 0 )) && echo zero
zero
$ (( $a == 0 )) && echo zero
bash: ((: == 0 : syntax error: operand expected (error token is "== 0 ")
Given that you already have $((...)) and ((...)) available for arithmetic, it's best to avoid -eq and the other arithmetic comparison operators inside [[; either use [ "$a" -eq 0 ] (which will raise an error if a is null or unset) or use (( a == 0 ).
It's well documented in the manual: 6.5 Shell Arithmetic
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax. A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syntax.
and
A null value evaluates to 0.
Your tests are running into the 2nd case.
I can't tell you the design rationale behind it. It is handy in practice though:
unset a
(( a++ ))
echo $a # => 1
Also, other languages do the same thing:
$ awk 'BEGIN {if (unset_variable == 0) print "zero"}'
zero
$ perl -E 'if ($unset_variable == 0) {say "zero"}'
zero
So, why do double brackets treat null and empty variables as being numerically equal to zero?
From bash shell manual 6.5 Shell Arithmetic:
A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syntax
The quotes inside bash extension [[ compound command do not matter, because word splitting expansion is not performed inside [[.
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
> [ 0 ]; echo $?
0
> [[ 0 ]]; echo $?
0
> (( 0 )); echo $?
1
> [ 1 ]; echo $?
0
> [[ 1 ]]; echo $?
0
> (( 1 )); echo $?
0
Is the behavior of (( 0 )) just to match the typical numerical value of false in other languages? IE where true == 1 and false == 0?
From the bash manual:
(( expression ))
The arithmetic expression is evaluated according to the rules described below (see Shell Arithmetic). If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1.
Your supposition is probably right, that it's to mimic the way many programming languages treat any non-zero value as truthy. You can then write:
if (( <expression> ))
then
...
fi
similar to how you would do it in C.
Barmar already gave the right reply for your main question.
A large part of your tests weren't about testing an arithmetic expressions value though but related to the test command [ or the ksh/bash conditional compound command [[.
In that case, if there is no operator, the test is string based, not arithmetic and the rule is very simple, everything not an empty string evaluates to true so only the empty string evaluates to false.
That's the reason all of your non arithmetic tests return a success status (0):
$ [ 0 ]; echo $?
0
$ [[ 0 ]]; echo $?
0
$ [ 1 ]; echo $?
0
$ [[ 1 ]]; echo $?
0
To get a failure status (1):
$ [ "" ]; echo $?
1
$ [[ "" ]]; echo $?
1
The test command accepts no string at all too:
$ [ ]; echo $?
1
but in that case the [[ command fails with both bash and ksh:
$ bash
$ [[ ]]; echo $?
bash: syntax error near ';'
$ ksh
$ [[ ]]; echo $?
ksh: syntax error: `;' unexpected
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.
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
$