318

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Victor Brunell
  • 5,668
  • 10
  • 30
  • 46

4 Answers4

428

= 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:

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
57

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 ==.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
dawg
  • 98,345
  • 23
  • 131
  • 206
  • I'm curious about the *technical reason* for `[[ "yes" -eq "no" ]]` returning True. How does bash coerce these strings to integer values that can be compared? ;-) – odony May 11 '17 at 14:42
  • 4
    Bash variables are [untyped](http://www.tldp.org/LDP/abs/html/untyped.html) so `[[ "yes" -eq "no" ]]` is equivalent to `[[ "yes" -eq 0 ]]` or `[[ "yes" -eq "any_noninteger_string" ]]` -- All True. The `-eq` forces integer comparison. The`"yes"` is interpreted as a integer `0`; the comparison is True if the other integer is either `0` or the string result is `0`. – dawg May 12 '17 at 19:55
  • 1
    Boo, hiss re: showing (nonportable) `==` in the code samples and only mentioning (portable, standardized) `=` underneath. – Charles Duffy Dec 05 '19 at 16:16
30

== 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" ]

Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
25

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
$
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bk-se
  • 540
  • 5
  • 7
  • 2
    Needs to be `[ "$lht" = "$rht" ]` **with the quotes** to be reliable even for equality. If you have a file created with `touch 'Afoo -o AB'`, `[ $lft = $rht ]` will return true, even though that file name is *not at all* identical to `AB`. – Charles Duffy Dec 05 '19 at 16:15