bash compound if statement with booleans - bash

I am trying to figure out why bash "miss-behaves":
REG=true
VIN=false
if $REG -a $VIN; then
echo $REG $VIN
fi
when run, it yields:
$ bash test.sh
true false
I was expecting nothing, can you show how to make a complete evaluation of both variables ?

In REG=true, 'true' is a string. When you do $REG, you're executing the command true.
if $REG -a $VIN means "run the command true with the option '-a' and argument '$VIN', which are ignored by true. So it's the same as if you had if true.
if $REG && $VIN causes 'true' and 'false' to be executed and their results 'and-ed', which yields false.
if [ "$REG" -a "$VIN" ] DOES NOT WORK (as defined here). This is the same as if test "true" -a "false". Any non-null string is true, so this statement is true. Same goes for if [[ "$REG" && "$VIN" ]]
However:
if [[ `$REG` && `$VIN` ]] # works
does work because here you execute the commands true and false.

Use && instead in the most POSIX way:
if [[ "$REG" && "$VIN" ]]; then
echo $REG $VIN
fi
If you want to use the -a, then surround with brackets:
if [ "$REG" -a "$VIN" ]; then
if $REG && $VIN; then

Correct way for you would be:
if [[ $REG = 'true' && $VIN = 'true' ]]; then
echo "$REG $VIN"
fi
This is the most correct and safe way (unlike executing your $REG and $VIN as other answers suggest). For example, what is going to happen if $REG variable is empty? Or what if it equals to something different than just true or false?
If you want boolean behavior in bash, then consider the fact that empty strings are falsy.
REG=1
if [[ $REG ]]; then
echo '$REG is true!'
fi
or like that for multiple variables:
REG=1
VIN=''
if [[ $REG && $VIN ]]; then
echo '$REG and $VIN are true!'
fi
if [[ $REG && ! $VIN ]]; then
echo '$REG is true and $VIN is false!'
fi
So, if you want something to be false, then either leave it unset or use an empty string.

Related

Multiple if condition in bash not working

I have written the following bash script:
if [ "crack" == "crack" -a "something/play" == *"play"* ];
then
echo "Passed"
else
echo "Failed"
fi
However the right side of this comparison is not working.
I noticed that if I use it the only right side with [[ "something/play" == *"play"* ]] it works correctly but how do I combine the two conditions inside the if clause.
It's a difference between [ and [[. The first is a standard command, where = just tests for equality. (Note that the standard operator is =, not ==.) The latter is a feature from ksh, supported in Bash and Zsh, and there, =/== is a pattern match. Also, you should avoid using -a within [ .. ], it can break if you do something like [ "$a" = foo -a "$b" = bar ] and $a or $b contains a !.
So,
$ if [[ "crack" == "crack" && "something/play" == *"play"* ]]; then echo true; fi
true
See also (in unix.SE): Why is [ a shell builtin and [[ a shell keyword? and What is the difference between the Bash operators [[ vs [ vs ( vs ((?.
If you use double brackets you can chain conditions with && (and) and || (or).
if [[ "crack" == "crack" && "something/play" == *"play"* ]]
then
echo "Passed"
else
echo "Failed"
fi

unset variable check in bash

I have two variables declared but unset:
__var1=
__var2=
Now I set __var2 to have some value:
__var2=1
When I try to do a check like this:
[ -z "$__var1" -a -z "$__var2" ] || echo "Both missing!"
I am getting that message Both missing!. But that's incorrect.
Why is that? And how to do a proper check, to see if both of them are missing?
And if the user wants to check if the variable is really unset and not just having an empty value, you can do:
$ A=1234
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ A=
$ [[ -z ${A+.} ]] && echo "Variable is unset."
$ unset A
$ [[ -z ${A+.} ]] && echo "Variable is unset."
Variable is unset.
In which in your case it could be
[[ -z ${__var1+.} && -z ${__var2+.} ]] && echo "Both variables are unset!"
#Dave Schweissguth's answer makes a good point about the logic of your code, but there are also things to observe about the syntax:
[Update: The original form of the question used assignments such as $__var1= - this has since been corrected] In Bourne-like/POSIX-compatible shells you do not use the $ prefix when assigning a value, only when referencing it; thus, your assignments should read:
__var1=
__var2= # or, later: __var2=1
Your question is tagged bash, so the best bash way to write your could would be:
[[ -z $__var1 && -z $__var2 ]] && echo "Both missing!"
Note the use of [[ ... ]] rater than [ ... ], which obviates the need to double-quote the operands to -z.
By contrast, the most portable (POSIX-compliant) way is:
[ -z "$__var1" ] && [ -z "$__var2" ] && echo "Both missing!"
Your code prints "Both missing!" if it's not true (||) that both (-a) variables are empty (-z). You want to print the message if that IS true. Do that like this:
[ -z "$__var1" -a -z "$__var2" ] && echo "Both missing!"
I don't recall ever seeing a version of bash or test (what sh uses to evaluate the same expressions) without -z or -a, so as far as I know the above will work on any Unix-like system you're likely to find.

Bash Boolean testing

I am attempting to run a block of code if one flag is set to true and the other is set to false. ie
var1=true
var2=false
if [[ $var1 && ! $var2 ]]; then var2="something"; fi
Since that did not evaluate the way that I expected I wrote several other test cases and I am having a hard time understanding how they are being evaluated.
aa=true
bb=false
cc="python"
if [[ "$aa" ]]; then echo "Test0" ; fi
if [[ "$bb" ]]; then echo "Test0.1" ; fi
if [[ !"$aa" ]]; then echo "Test0.2" ; fi
if [[ ! "$aa" ]]; then echo "Test0.3" ; fi
if [[ "$aa" && ! "$bb" ]]; then echo "Test1" ; fi
if [[ "$aa" && ! "$aa" ]]; then echo "Test2" ; fi
if [[ "$aa" ]] && ! [[ "$bb" ]]; then echo "test3" ; fi
if [[ "$aa" ]] && ! [[ "$cc" ]]; then echo "test4" ; fi
if [[ $aa && ! $bb ]]; then echo "Test5" ; fi
if [[ $aa && ! $aa ]]; then echo "Test6" ; fi
if [[ $aa ]] && ! [[ $bb ]]; then echo "test7" ; fi
if [[ $aa ]] && ! [[ $cc ]]; then echo "test8" ; fi
When I run the preceding codeblock the only output I get is
Test0
Test0.1
Test0.2
however, my expectation is that I would get
Test0
Test1
Test3
Test5
Test7
I have tried to understand the best way to run similar tests, however most examples I have found are set up in the format of
if [[ "$aa" == true ]];
which is not quite what I want to do. So my question is what is the best way to make comparisons like this, and why do several of the test cases that I would expect to pass simply not?
Thank you!
Without any operators, [[ only checks if the variable is empty. If it is, then it is considered false, otherwise it is considered true. The contents of the variables do not matter.
Your understanding of booleans in shell context is incorrect.
var1=true
var2=false
Both the above variables are true since those are non-empty strings.
You could instead make use of arithmetic context:
$ a=1
$ b=0
$ ((a==1 && b==0)) && echo y
y
$ ((a==0 && b==0)) && echo y
$
$ ((a && !(b))) && echo y; # This seems to be analogous to what you were attempting
y
The shell does not have Boolean variables, per se. However, there are commands named true and false whose exit statuses are 0 and 1, respectively, and so can be used similarly to Boolean values.
var1=true
var2=false
if $var1 && ! $var2; then var2="something"; fi
The difference is that instead of testing if var1 is set to a true value, you expand it to the name of a command, which runs and succeeds. Likewise, var2 is expanded to a command name which runs and fails, but because it is prefixed with ! the exit status is inverted to indicate success.
(Note that unlike most programming languages, an exit status of 0 indicates success because while most commands have 1 way to succeed, there are many different ways they could fail, so different non-zero values can be assigned different meanings.)
true and false are evaluated as strings ;)
[[ $var ]] is an equivalent of [[ -n $var ]] that check if $var is empty or not.
Then, no need to quote your variables inside [[. See this reminder.
Finally, here is an explication of the difference between && inside brackets and outside.
The closest you can come seems to be use functions instead of variables because you can use their return status in conditionals.
$ var1() { return 0; }
$ var2() { return 1; } # !0 = failure ~ false
and we can test this way
$ var1 && echo "it's true" || echo "it's false"
it's true
$ var2 && echo "it's true" || echo "it's false"
it's false
or this way
$ if var1; then echo "it's true"; else echo "it's false"; fi
it's true
$ if var2; then echo "it's true"; else echo "it's false"; fi
it's false
Hope this helps.

Regarding Bash substring comparison

I try to test if a string starts with a certain prefix. But my script seems not work (I would expect the "if" branch will not get run). Can some Bash expert help to take a look? thanks!
Here is my code and test result:
$ cat testb.bash
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}"=="order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [[ "${my_var:0:5}" -eq "order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [ "${my_var:0:5}"="order" ]; then
echo "value of my_var is ${my_var}.";
fi;
$ bash -x testb.bash
+ my_var=abcdefg
+ [[ -n abcde==order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ [[ abcde -eq order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ '[' abcde=order ']'
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
$
Whitespace is significant in this case. As you can see in the -x output, it understands the first condition as
[[ -n "${my_var:0:5}==order" ]]
Moreover, to test for a prefix, you can use a pattern:
[[ $my_var == order* ]]
To test the existence of substring, you can use either of these:
if [[ "$j" =~ string1 ]]; then
if [[ $j == *string1* ]]; then
In your particular case, you miss a space surounding ==, so instead of
if [[ "${my_var:0:5}"=="order" ]]; then
it should be
if [[ "${my_var:0:5}" == "order" ]]; then
^ ^
Finally, note that your condition was evaluated as true because it was evaluating if [ "string" ], which is true if string is not empty:
$ [ "a" ] && echo "yes"
yes
Test
$ cat a
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}" == "order" ]]; then
echo "value of my_var is ${my_var}."
elif [[ "${my_var:0:5}" == "abcde" ]]; then
echo "yeahaa"
else
echo "is not"
fi
$ ./a
yeahaa
Ok, i tested your code, you shoud such as the following code:
prefix="pre_order";
pre="pre_"
len=${#pre}
echo $len
if [[ "${prefix:0:len}" == "blahvlah" ]] ; then
echo "dddd"
fi;
Notes:
use == for string comparation
for ${} you should initilize a string variable before ${}
use len=${#pre} for lenght of string.
A POSIX-compliant way to test for a prefix is to attempt to remove the prefix, and compare the result to the original string. If the two are the same, the prefix is not present, the removal fails, and the expression expands to the original string.
prefix=foo
string=foobar
if [ "${string#$prefix}" = "$string" ]; then
printf "$string does not start with $prefix\n"
else
printf "$string starts with $prefix\n"
fi

OR logical operator in bash

I have very dumb problem but can't wrap my head around it
if [[ false || false ]] ; then
echo 'true'
else
echo 'false'
fi
As per http://tldp.org/LDP/abs/html/comparison-ops.html
-o logical or
exp1 -o exp2 returns true if either exp1 or exp2 is true.
These are similar to the Bash comparison operators && and ||, used
within double brackets. [[ condition1 && condition2 ]]
so if both are false then it should return false? then why it prints 'true'?
You should run those not as part of the conditional command '[[ ]]':
if false || false; then
echo 'true'
else
echo 'false'
fi
As for testing falses within [[ and ]]:
if [[ ! 1 || ! 1 ]]; then
echo 'true'
else
echo 'false'
fi
Noting that [[ false ]] is equivalent to [[ -n false ]] which makes a true condition.
If you like you could make a more apparent and valid conditional test with (( )) like this:
if (( 0 || 0 )); then
echo 'true'
else
echo 'false'
fi
"false" is not false. "false" is a non-empty string. Non-empty strings are true by default in [[.

Resources