Why does adding spaces around bash comparison operator change the result? - bash

Could someone explain why spaces around == change the comparison result? The following:
if [[ 1 == 2 ]] ; then echo ok ; fi
prints nothing, while
if [[ 1==2 ]] ; then echo ok ; fi
prints ok

"1==2" is a single 4-character string, not an expression involving the == operator. Non-empty strings always evaluate to true in the context of the conditional expression [[ ... ]]. Whitespace is mandatory around the == operator.
Like everything else in bash, the contents of [[ ... ]] are simply a white-space-separated list of arguments. The bash grammar doesn't know how to parse conditional expressions, but it does know how to interpret a list of 3 arguments like 1, ==, and 2 in the context of the [[ ... ]] compound command.

Because it's just a string, consider testing :
[[ foobar ]]
it will be true.
This is useful to test if a variable is set or not like in this example :
x='foobar'
[[ $x ]] # true
and now
x=''
[[ $x ]] # false
Finally
The spaces are mandatory in a test expression

Related

Bash substring test detecting empty string as a substring of a non-empty string

Consider this...
#!/bin/bash
declare STR='test'
declare SUB=''
[[ "${STR}" == *"${SUB}"* ]] && echo 'match'
This appears to resolve or evaluate to true? This makes no sense to me.
To get the expected result you have to test if the SUB string is empty?
#!/bin/bash
declare STR='test'
declare SUB=''
[[ ! -z "${SUB}" ]] && [[ "${STR}" == *"${SUB}"* ]] && echo 'match'
Is this some quirk with BASH or such? If the sub string is NOT in the string should it not return false?
The empty string is part of every string. Thus, testing whether an empty string is a substring of anything else is always true.
This is not specific to bash or in any way unusual; you can check it in other languages.
Python: print('' in 'foo') prints True.
Java: String("foo").contains("") returns true.
C: strstr("foo", "") returns a pointer to the first character in foo, meaning it found a match.
Ruby: "foo".include? "" returns true.
Yes, you should explicitly check for a possible empty string, but I'd use -n instead of a not, and in bash you can make it a single compound condition check.
$: x=abcdefg
$: y=cde
$: [[ -n "$y" && "$x" =~ $y ]] && echo match || echo no
match
$: y=
$: [[ -n "$y" && "$x" =~ $y ]] && echo match || echo no
no
...and you don't need the leading and trailing globs, they don't really add anything.

bash [[ [a] == [a] ]] not true? square bracket affect compare result

Anyone know why this happens? Is this a bug of bash?
x='mnt:[4026532411]'
[[ $x == $x ]] && echo OK
I am expecting result OK, but it did not.
Of course, this works
[[ "$x" == "$x" ]] && echo OK
But as I know, bash [[ ]] have a merit that no need to quote var when compare.
x='a b'
[[ $x == $x ]] && echo OK
works.
Ironical things is
x='mnt:[4026532411]'
[[ $x != $x ]] && echo Oh my god
result is Oh my god
The unquoted right-hand side of == and != is treated as a pattern, not a literal string. mnt:[4026532411] will match mnt: followed by exactly one of 0, 1, 2, 3, 4, 5, or 6, since the patterns mnt:[4026532411] and mnt:[0123456] are equivalent. To match the lieral string, you need to quote the expansion.
x='mnt:[4026532411]'
[[ $x == "$x" ]] && echo OK
What you are seeing is do do this sentence from the bash man page:
When the == and != operators are used, the string to the right of
the operator is considered a pattern and matched according to the
rules described below under Pattern Matching, as if the extglob
shell option were enabled.
As you may already know, [...] in the shell allows matching from a
group of characters. That is, given the files:
$ ls
fileA fileB fileC fileD
Running ls file[AB] will yield:
fileA fileB
So in your expression, mnt:[1234] is interpreted in a similar
fashion.

Why does this if statement get evaluated to true in bash?

# prints "here" when run in bash
if [[ ((9 > 220)) ]]; then echo "here"; fi
I'm confused why the above if statement gets evaluated to true. Wouldn't ((9 > 220)) evaluate to false which would make the if statement false?
The code below behaves as expected. I'm confused why using the double parentheses in a double brackets "if" isn't working above though.
# doesn't print anything
if ((9 > 220)); then echo "here"; fi
There's a fundamental difference between those two compound commands inside a conditional construct.
[[ expression ]] compound command
In your first example you're using the [[ expression ]] command:
if [[ ((9 > 220)) ]]; then echo "here"; fi
where parenthesis are treated merely as grouping operators, used to override the normal precedence of other operators (like !, &&, ||, >, -gt, -e, etc.). The > operator in this case is a lexicographic greater than.
This is nicely described in man bash:
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the conditional expression expression. Expressions are composed of the primaries described below under CONDITIONAL EXPRESSIONS. Word splitting and pathname expansion
are not performed on the words between the [[ and ]]; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed. Conditional operators such as -f must be unquoted to be recognized as primaries.
When used with [[, the < and > operators sort lexicographically using the current locale.
So, to compare integers in the [[ compound command, you can use the conditional expression operators, the same one used by test and [ commands. For example like this:
if [[ 9 -gt 220 ]]; then echo "here"; fi
The result is the same like when the -gt operator is grouped with parenthesis:
if [[ ((9 -gt 220)) ]]; then echo "here"; fi
Alternatively, you can use the arithmetic expansion and exploit the fact that the boolean results are represented as "0" or "1":
if [[ $((9 > 200)) == 1 ]]; then echo "here"; fi
(( expression )) compound command
In your second example, you're using the (( expression )) command:
if ((9 > 220)); then echo "here"; fi
where the expression is evaluated according to the rules of the shell arithmetic. The man bash says:
((expression))
The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent
to let "expression".
It is because using double brackets makes it a lexicographical comparison. That means that it checks for which sequence is longer, like sorting alphabetically. More information about lexicographical comparisons here
Inside of [[ ]], the parentheses don't trigger an arithmetic context, they're interpreted purely for grouping/precedence1. All of these are equivalent:
$ [[ 0 < 1 ]] && echo yes
yes
$ [[ (0 < 1) ]] && echo yes
yes
$ [[ ((0 < 1)) ]] && echo yes
yes
$ [[ (((0 < 1))) ]] && echo yes
yes
$ [[ ((((0 < 1)))) ]] && echo yes
yes
If we have unbalanced parentheses, Bash complains about that:
$ [[ ((((0 < 1))) ]] && echo yes
bash: unexpected token `]]', expected `)'
bash: syntax error near `]]'
This all being said, < and > within [[ ]] are for lexicographical string comparison, so the statement above just checks if 0 is lexicographically sorted before 1 (it is).
Observe:
$ [[ 11 < 2 ]] && echo yes
yes
To compare numbers, you have to use -gt, -lt, -ge, -le, eq, ne instead of >, <, >=, <=, =, !=:
$ [[ 11 -lt 2 ]] && echo yes || echo no
no
Since you're already using (( )), you could use just that for comparing numbers:
$ (( 11 < 2 )) && echo yes || echo no
no
This is the simplest and clearest method in my opinion, if you know you have Bash at your disposal.
1 See the manual about [[ ]]:
Expressions may be combined using the following operators, listed in
decreasing order of precedence:
( expression )
Returns the value of expression. This may be used to override the normal precedence of operators.
Hat tip to glenn jackman for pointing to the manual in his comment to another answer.
The double parentheses is an arithmetic context. If you use the brackets, you have to use -lt for less than and -gt for greater than. See Comparing numbers in Bash.

How to use arithmetic in an if statement for Bash

Hi Guys I am trying to perform a check in my bash script that needs to meet three conditions, I was able to make the first 2 conditions work in the if statement as I wanted, but when I wanted to implement a third check with some arithmetic using the && operator, the script does not even launch.
DOUBLE_CHECK=0
if [[ -z "$avail" && "$WIFI_ID" == "some_str" && 'expr $DOUBLE_CHECK % 2' -eq "0"]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
The Idea is I want the third check to have a number that increments over time inside my while loop and then checked, whenever it is divisible by 2 it passes the 3rd condition of the if statement
As noted in a separate answer, you could use backticks or preferably $() to expand the expression to the output of the inner command, like this :
if [[ -z "$avail" && $WIFI_ID == "some_str" && $(expr $DOUBLE_CHECK % 2) -eq "0" ]]
Please note I have added a (required) space before the final ]].
Another possibility is to use an arithmetic expression :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ((DOUBLE_CHECK % 2 == 0))
Please note I have removed harmless but unnecessary quotes : the double-bracketed conditional expression is not a normal command, it is special shell syntax, and it does not perform word splitting. I have left quotes around some_str, because equality/inequality comparisons will perform pattern matching on the right-hand expression if it is not quoted. There is also no $ before the variable name in the arithmetic expression : it works, but is not required inside (()) or $(()).
The expression could also be expressed as :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ! ((DOUBLE_CHECK % 2))
The reason for this is that (( )) has a return code of 0 if its numerical result is non-zero, and a non-zero return code otherwise.
There are a couple small issues
Your third condition should be evaluated using back-ticks ('`'), instead of quotes. Single-quotes in bash mean string literal. So, 'expr $DOUBLE_CHECK %2' -eq "0" will always be false
The check -eq is used to compare numbers. Your statement is using strings (to compare strings, use the normal == syntax)
Please try,
DOUBLE_CHECK=0
if [[ -z "$avail" ]] && [[ "$WIFI_ID" == "some_str" ]] && [[ `expr $DOUBLE_CHECK % 2` -eq 0 ]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
You can use [[ .. ]] for string logic (where = and == are equivalent) and (( ... )) for numeric comparison, and do away with expr:
if [[ -z "$avail" && "$WIFI_ID" = "some_str" ]] && (( DOUBLE_CHECK % 2 == 0 )); then
# your logic here
fi
Note that prefixing variables with $ is optional inside (( ... )) and quoting variables is optional inside [[ ... ]] since doesn't do word splitting and globbing there.

Are = and == different when comparing strings in the bash [[ ]] form?

when I compare two strings with single equal “=” ?
and when I compare two strings with double equal “==” ?
for example:
[[ $STR = $STR1 ]]
OR
[[ $STR == $STR1 ]]
Or maybe they are both do exactly the same thing?
[[ $a == z* ]] # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
[ $a == z* ] # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).
Everything about string comparison you can find here.
You right, both way does exactly the same thing, there is no difference at the execution.
In your conditions, take care to add quotes to your variable's name, bash could throws an error if one of them is null. Add double quotes will pass throught this error by setting variable to empty string "".
[[ "$STR1" = "$STR2" ]]
EDIT : (Thanks to comments below)
Prefer use this syntaxe [ "$STR1" == "$STR2" ] for test and shell convenience. Doubles quotes are better to use and make your condition usable with regular expression as "*.txt" but not even required.

Resources