OR logical operator in bash - 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 [[.

Related

Arithmetic expression inside [[

I have this code:
fruit=apple
flag=0
[[ $fruit = "apple" && ((flag == 0)) ]] && echo "1"
[[ $fruit = "apple" && (($flag == 0)) ]] && echo "2"
[[ $fruit = "apple" && ((! flag)) ]] && echo "3"
[[ $fruit = "apple" && ((! $flag)) ]] && echo "4"
All of them are expected to echo something. However, only the second statement works properly:
[[ $fruit = "apple" && (($flag == 0)) ]] && echo "2"
Why is this? Won't arithmetic expressions work properly inside [[ ]]?
The == works the same as = - it is a string comparision.
The ( ) inside [[ ]] is used to nest expressions. Ex. [[ a = a && (b = b || c = d) ]]
A non empty string is true. So [[ some_string ]] or [[ "some other string" ]] returns true. [[ "" ]] or [[ '' ]] returns false.
The ! is negation.
[[ $fruit = "apple" && ((flag == 0)) ]]
First the expansion of $fruit happens. So it becomes: [[ "apple" = "apple" && ( ( "flag" == "0" ) ) ]]. The ( ) are just braces, this is not arithemtic expansion. The string "apple" is equal to "apple", but the string "flag" is not equal to string "0", so it always returns with false.
[[ $fruit = "apple" && (($flag == 0)) ]]
Because $flag is expanded, the [[ sees: [[ "apple" = "apple" && ((0 == 0)) ]]. So the comparision happens to work, however == is doing string comparision.
[[ $fruit = "apple" && ((! flag)) ]]
The flag is a nonempty string, so it evaulates to true. The ! flag evaulates to false.
[[ $fruit = "apple" && ((! $flag)) ]]
First $flag is expanded to 0. As 0 is a nonempty string, it evaluates to true. The ! 0 evaluates to false. The ((! 0)) is false, so the whole expression returns nonzero.
Won't arithmetic expressions work properly inside [[ ]]?
No, arithmetic expressions will not work inside [[ ]] the same way [[ echo 1 ]] does not work. The echo 1 ]] are arguments of [[ builtin, not a standalone command. The same way [[ (( 0 )) ]] the (( 0 )) ]] are interpreted as arguments to [[.
((...)) is an arithmetic command (or statement); the arithmetic expresson is $((...)). You want
[[ $fruit = "apple" ]] && ((flag == 0)) && echo 1
# etc.
[[ $fruit = apple && $((flag == 0)) ]] would fail because [[ ... ]] would simply treat the evaluation of the arithmetic expression as a non-empty string (which is always true) rather than a boolean value.
For completeness, you could use a single [[ ... ]] command:
[[ $fruit = apple && flag -eq 0 ]] && echo 1
# etc

bash if statement with strings always evaluates to true

I am getting started with bash and am having trouble with if statements.
Why does the following script:
#!/bin/bash
read C
if (( $C == 'Y' )) ; then
echo "YES"
elif (( $C == 'N' )) ; then
echo "NO"
fi
Seem to print YES no matter what value $C takes on.
Strings inside the arithmetic statement ((...)) are recursively expanded until you either get an integer value (including 0 for an undefined parameter) or a string that causes a syntax error. Some examples:
# x expands to y, and y expands to 3
$ x=y y=3
$ (( x == 3 )) && echo true
true
$ x="foo bar"
$ (( x == 3 ))
bash: ((: foo bar: syntax error in expression (error token is "bar")
# An undefined parameter expands to 0
$ unset x
$ (( x == 0 )) && echo true
true
In your case, $C expands to some undefined parameter name, and both it and Y expand to 0, and 0 == 0.
For string comparison, use [[ ... ]] instead.
if [[ $C == Y ]]; then
Yep, as #larsks mentioned, you need the square brackets. Try this full version:
#!/bin/bash
read C
if [[ ${C} == 'Y' ]]; then
echo "YES"
elif [[ ${C} == 'N' ]]; then
echo "NO"
fi
Here is the right format.
#!/bin/bash
read C
if [[ $C == 'Y' ]]
then
echo "YES"
elif [[ $C == 'N' ]]
then
echo "NO"
fi

Multiple AND conditions in bash that work independently based on an OR condition separator?

This question is different from the multitude of potential duplicates, and I've not been able to find this particular question answered or even asked...
Maybe I'm overlooking some simple logic here, but I'm running into the following issue:
I'm trying to have an if statement where conditions must be met like $V1 AND $V2 are TRUE || OR $V3 AND $V4 are TRUE. Here's a simple test:
#!/bin/bash
V1="File Placeholder"
#echo $V1
V2="May contain some text"
#echo $V2
V3="Some command output"
#echo $V3
V4="Command output contains this text"
#echo $V4
if [[ "$V1" ]] && [[ "$V2" == *"contain some"* ]] || [[ "$V3" ]] && [[ "$V4" == *"output contains"* ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
Meaning, I'd like to do something if:
$V1 is true (a file exists)
AND
$V2 is true (some string is found)
OR
$V3 is true (i.e., not null)
AND
$V4 is true (command output contains text)
It appears to work a bit, but I realize it's not working properly: it won't return TRUE if both the second && conditions are FALSE ie: || [[ "$V6" ]] && [[ "$V4" == *"output contains"* ]] (why I think I may be overlooking some logic, maybe getting cancelled out somehow?).
Why isn't this working as I assume it would if a AND b are TRUE ... OR ... if x AND z are TRUE?
You should be using this snippet to group && conditions together in one [[ ... ]].
if [[ -n $V1 && $V2 == *"contain some"* ]] || [[ -n $V3 && $V4 == *"output contains"* ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
Boolean conditions stop processing as soon as they have enough information to satisfy the test. So, if you have something like true && false && true, the last true will never be reached. A single false is enough to know that the whole test is going to be false, since you have to have all trues.
You might try adding (), which creates a subshell for each. This should effectively group the tests together, and allow the larger tests to be separate:
if ( [[ "$V1" ]] && [[ "$V2" == *"contain some"* ]] ) || ( [[ "$V3" ]] && [[ "$V4" == *"output contains"* ]] )
then
echo "Hello $V1"
echo "World full of: $V2"
fi
The important thing is to remember to keep your conditions separate.
I ultimately chose to use an 'order of evaluation' approach as firstly commented by #Lino referencing the third example in this answer groups of compound conditions in Bash test
if [[ "$V6" && "$V2" == *"contain some"* || ( "$V3" && "$V4" == *"output contains"* ) ]]
then
echo "Hello $V1"
echo "World full of: $V2"
fi
If this is a bad approach for some reason, I would appreciate any feedback.
I like how it is all contained within a single [[ ]] double bracket, and remains readable.
All answers and comments have been very helpful and enlightening! #DKing's answer suggesting to use subshells, #anubhava's answer cleanly combining the statements, and all of #chepner's comments!

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.

bash compound if statement with booleans

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.

Resources