Shell equality operators (=, ==, -eq) - bash

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
$

Related

How to define and process the opposite of a long list argument in bash

How to define and process the opposite of a long list argument in bash ?
In bash you can do it with Arithmetic Expansion if you only have arithmetical conditions:
#!/bin/bash
var4=10
var1=9
var3=2
var2=1
# Arithmetic Expansion:
# set condition to 1 if it's true or 0 if it's not
condition=$(( var4 < var1 || var3 > var2 ))
# != is "not equal to"
if [[ $condition != 1 ]]; then
echo "! cond"
else
echo "cond"
fi
The condition testing could also be done like this:
if ! ((condition)); then
where ! stands for not
In Bourne shell [ is a command (see with type [). Actually it's another name for the test command.
Let's take your example:
if [ "$var4" -lt "$var1" ]; then ...; fi
($ were added by me since those are variables)
But what if I have many conditions...
The test and [ commands accept an arbitrary list of conditions:
if [ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]; then ...; fi
(-o is a logical OR)
... and would like to take the opposite of the result of all of them like
Like any command in Bourne shell, you can test for the opposite outcome (command did not return OK) with the ! sign:
if ! [ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]; then ...; fi
Alternatively, the test and [ commands also have a NOT operator (written !):
if [ ! '(' "$var4" -lt "$var1" -o "$var3" -gt "$var2" ')' ]; then ...; fi
(you have to use parentheses here since you want to negate the whole compound expression)
Further readings with man test and help test.
Of course, you can do it in two steps like this:
[ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ]
condition=$?
# Now condition contains the result of the [ command,
# which is 0 if it the test is true and 1 otherwise
if [ "$condition" -ne 0 ]; then ...
Admittedly, the [ "$condition" -ne 0 ] syntax is counter-intuitive and less readable; one may prefer Ted Lyngmo's answer instead.
Another more readable alternative:
condition=false
[ "$var4" -lt "$var1" -o "$var3" -gt "$var2" ] && condition=true
# "true" and "false" being two Unix commands,
# we can now use condition like this:
if ! "$condition"; then ...
Since you specify bash, you can do it arithmetically -
if (( var4 < var1 )) || (( var3 > var2 )); then echo true; else echo false; fi
or all at a go --
if [[ "$var4" -lt "$var1" || "$var3" -gt "$var2" ]]; then echo true; else echo false; fi
If you use the latter, < and > evaluate their args as strings and compare them by lexigraphic sorting, which should be LOCALE specific.
c.f. https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
To get the "not" of a condition, predicate with !.
To get a compound condition as a unit you can collectively invert with that not, enclose in parens.
if [[ ! ( "$var4" -lt "$var1" || "$var3" -gt "$var2" ) ]]; then echo true; else echo false; fi
As for your question - " is there a way to do like python, defining a variable for big list of conditions and doing a not on that condition ?" - I suspect your assumption is imprecise. Python does not put all the compound conditions in the variable, it evaluates them and saves the boolean result, which the if then parses, and can negate.
It sounds like what you want to do is set up a series of tests, save the boolean, then get the reverse of that. In bash, if that's just the structure you want, I'd do something like this -
[[ ( "$var4" -lt "$var1" || "$var3" -gt "$var2" ) ]]; # set return code
if (( $? )) ... # check as math
The true return from a normal conditional is a zero (0), which evaluates to false in arithmetic context. Any other response (nonzero) evaluates as boolean false in a normal [[...]] return code, but true in arithmetic context, so evaluating $? in an arithmetic context you are implicitly applying a "not".
You can add a step between to save $? to another variable, but the result is the same. Just remember that any time you do this, you are flipping from return code testing to arithmetic testing, so it ALWAYS adds the not implicitly. That's a documentation and maintenance nightmare. I strongly recommend against it for that reason.

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.

Why does `(( 0 ))` have a return value of 1 in .sh script?

> [ 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

What is the difference between IF [ condition ] & [[ condition ]] , -eq and == in bash

Is there a difference between the two code snippets below
if [[ $a == "1" ]];then
echo $a
and
if [ $a == "1" ];then
echo $a
Also, is there a difference when I use -eq in place of == in the above snippet?
As for your main question: it is a duplicate of: Is [[ ]] preferable over [ ] in bash scripts?
You can also find a (hopefully) comprehensive discussion of the differences between [ ... ] and [[ ... ]] in this answer of mine.
In short: [[ ... ]] is parsed more like you'd expect in a regular programming language, and it implements many useful extensions, but it is not POSIX-compliant.
As for "is there a difference when I use -eq in place of ==?":
= and ==, its Bash alternative, perform string comparison.
Additionally, inside [[ ... ]] only, if the RHS of = or == is unquoted, it is interpreted as a glob-style pattern to match the LHS against; contrast [[ 'a' == '*' ]] && echo match with [[ 'a' == * ]] && echo match
Note that if you use [ ... ] (rather than [[ ... ]]) for POSIX compliance (portable use with /bin/sh), you should only use =, not ==; while Bash accepts == inside [ ... ] too, other shells don't.
-eq performs integer comparison
Other string/numeric operator pairs exist (e.g., -lt for numeric less-than vs. < for alphabetical (string) less-than, based on textual sort order).
Bash Conditional Expressions lists all operators you can use inside [ ... ] and [[ ... ]] (and also with test, which is effectively an alias of [ ... ]).
Additionally, inside [[ ... ]], regular expression-matching operator =~ is available - see Bash Conditional Constructs
In bash, numeric comparison is handled differently than string comparison
For numbers,
$var1 -eq $var2 // =
$var1 -gt $var2 // >
$var1 -ge $var2 // >=
$var1 -lt $var2 // <
$var1 -le $var2 // <=
$var1 -ne $var2 // !=
For strings
$str1 = $str2 // they are equal
str1 != str2 // not equal
str // Returns True if str is not null.
-n str // Returns True if the length of str is greater than zero.
-z str // Returns True if the length of str is equal to zero.
Note that == is the same as =
Also note that the == operates differently in a double bracket comparison (this is where your [ condition ] vs [[ condition ]] question comes in) when doing pattern matching. These comparisons/operators all all explained at http://www.tldp.org/LDP/abs/html/comparison-ops.html

How to test strings for lexicographic less than or equal in Bash?

In Bash, is there a simple way to test if one string is lexicographically less than or equal to another?
I know you can do:
if [[ "a" < "b" ]]
for testing strict inequality, or
if [[ 1 -le 1 ]]
for numbers. But -le doesn't seem to work with strings, and using <= gives a syntax error.
Just negate the greater than test:
if [[ ! "a" > "b" ]]
You need to use || with an additional condition instead of <=:
[[ "$a" < "$b" || "$a" == "$b" ]]
You can flip the comparison and sign around and test negatively:
$ a="abc"
$ b="abc"
$ if ! [[ "$b" > "$a" ]] ; then echo "a <= b" ; fi
a <= b
If you want collating sequence of "A" then "a" then "B"... use:
shopt -s nocaseglob
If you can use the Bash syntax, see the answers from #anubhava and #gordon-davisson. With POSIX syntax you have two options (note the necessary backslashes!):
using the -o operator (OR):
[ "$a" \< "$b" -o "$a" = "$b" ] && echo "'$a' LTE '$b'" || echo "'$a' GT '$b'"
or using negation:
[ ! "$a" \> "$b" ] && echo "'$a' LTE '$b'" || echo "'$a' GT '$b'"
I prefer the first variant, because imho it's more readable.
expr POSIX method
I believe [ a < b ] is a Bash extension. The best POSIX method I could find was this as documented at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html:
expr abc \< acb >/dev/null || echo fail
! expr abc \< aac >/dev/null || echo fail
or with slightly worse golfing and a subshell:
[ "$(expr abc \< acb)" = 1 ] || echo fail
[ "$(expr abc \< aac)" = 0 ] || echo fail
But because expr \< is completely insane and:
automatically determines is something is an integer or not to choose numerical vs lexicographical comparison, e.g. expr 2 \< 10 is 1
has undefined behaviour for magic keywords length, substr, index and match
you generally want to add a trash x to force your variable to be a non-reserved string as in:
x1=aac
x2=abc
x3=acb
expr x"$x2" \< x"$x3" >/dev/null || echo fail
! expr x"$x2" \< x"$x1" >/dev/null || echo fail
and so for less than or equal I'd just:
expr x"$x1" \< x"$x2" >/dev/null || [ "$x1" = "$x2" ] || echo fail
sort POSIX workaround
Just for fun, use expr instead.
Not infinitely robust to strings with newlines, but when is it ever when dealing with shell scripts?
string_lte() (
s="$(printf "${1}\n${2}")"
if [ "$(printf "$s" | sort)" = "$s" ]; then
exit 0
else
exit 1
fi
)
string_lte abc adc || echo fail
string_lte adc adc || echo fail
string_lte afc adc && echo fail

Resources