New Document
Integer Comparisons and their Gotchas

You can compare integers is shell in three different ways: using  new style ((...)) notation and two old style notations [[...]] or [...]. Only new style ((..)) notation can be recommended for this purpose.

Here is the full annotated list of available possibilities:

  1. Double round brackets conditionals This is a newer construct and it is the best of three available. They are covered in in detail in Arithmetic Expressions in BASH
  2. Double square bracket conditionals This is older construct that currently makes sense only for string comparisons.
  3. Single square brackets conditionals. This is the oldest construct that does not make sense anymore for integer comparisons.

In its turn Double round brackets conditionals can be classified into two types:

  • Those which use dollar sign in front of variables
  • Those that do not use dollar sign in front of variable

To preserve sanity it is better to use only "double round bracket comparison" without using dollar sign prefix before variables. This flavor of the  "double round bracket" construct  is supported in bash and all implementations of ksh in major Unixes so it is perfectly portable.   Here is short summary of operators for "double round bracket comparison"

  • <  is less than (within double parentheses). For example, (( a < b ))
  • <=  is less than or equal to (within double parentheses) (( a <= b ))
  • >  is greater than (within double parentheses) (( a > b ))
  • >=  is greater than or equal to (within double parentheses) (( a >= b ))

In addition, omitting "$" sign before variable helps to avoid some stupid legacy behaviors that plague shell due to its way too long historical development path.

Tip: Never use old-style shell comparison constructs (single square bracket comparison construct and double square bracket construct) unless absolutely necessary. The  "double round bracket" construct is much safer -- it contain far less Gotchas and is portable between bash and ksh88/ksh93.

Dealing with legacy integer comparisons in old scripts

You often need to deal with old scripts that use either [[..]] or even older [..] construct for integer comparisons. With [..] situation is pretty simple -- you can convert it to [[..]]. With [[..]] situation is more complex and in large scripts it is too much trouble to convert those construct into more modern ((..)) construct.

One unpleasant reality (and probably the most common gotcha) of using legacy integer comparison constructs is that if one of the variable is not initialized it produces syntax error.  That's usually when you need to look and determine "what's wrong" in the legacy scripts. Two classic tricks are used to suppress this nasty macro substitution side effect without going into in-depth analysis of logic ( They can be and often are used simultaneously):

  • Put variables in double quotes if they were "naked" variables ("quoting trick"). For example if [[ "$a" -eq "$b" ]]...
  • Prefix quoted variables with 0, for example to use  [[ "0$a" -eq "0$b" ]] instead of  [[ "$a" -eq "$b" ]]

You can also try to initialize offending variables explicitly, but that's usually more involved. Please note that you can't change "legacy integer comparison operators" to normal in most cases, althouth bash 4.2 provides a path for such a change. Here is the list for your reference: 

  • -eq  is equal to if [[ "$a" -eq "$b" ]]
  • -ne is not equal to if [[ "$a" -ne "$b" ]]
  • -gt is greater than if [[ "$a" -gt "$b" ]]
  • -ge is greater than or equal to if [[ "$a" -ge "$b" ]]
  • -lt is less than if [[ "$a" -lt "$b" ]]
  • -le is less than or equal to if [[ "$a" -le "$b" ]]
Dollar sign caveat in double round bracket comparison construct
If variable is used with leading dollar sign in double round bracket comparison construct and it is not initialized it will be substituted as null string like in old constructs and cause syntax errors. That's why we recommended remove dollar sign from all variables in  double round bracket comparison construct.

Here is a small test that illustrated the error (note, variable $a is not initialized)

# cat
if (( $a == 4 )) ; then
        echo this is what macro substitution is about
Results of execution in bash:
bash -xv
if (( $a == 4 )) ; then
        echo this is what macro substitution is about
+ ((   == 4  )) line 1: ((: == 4 : syntax error: operand expected (error token is "== 4 ")

If we remove leading $ from variable $a bash behaves pretty reasonably:

# bash -xv
if (( a == 4 )) ; then
        echo this is what macro substitution is about
+ ((  a == 4  ))
If we replace constant 4 with constant zero, then statement will be executed as we would expect from any decent scripting language:
# bash -xv
if (( a == 0 )) ; then
        echo this is what macro substitution is about
+ ((  a == 0  ))
+ echo this is what macro substitution is about
this is what macro substitution is about

You can avoid such a behaviors in older constructs by initializing variable beforehand. Two methods can be used to detect uninitialized variable: 

  1. Conditional with  operator -z (see below):

    [[ -z "$a" ]] && $a=0
  2. Operator ${foo:-bar} either directly in comparison or beforehand:
    if [[ ${a:-0} -gt 0 ]] ...

    Here is small test that illustrates behavior of uninitialized variables on double parenthesis comparison

cat ""
if [[ -z "$a" ]] ; then
   echo variable '$a' is not defined
if (( a == 0 )) ; then
   echo "this is how to avoid macro substitution pitfall"
Results of execution in ksh  produces an error "a == 0 : bad number" but bash is OK:
export PATH
+ [[ -z  ]]
+ echo variable $a is not defined
variable $a is not defined
+ let  a == 0[4]:  a == 0 : bad number
String comparison operators and their Gotchas

String comparison operations in shell are discussed in Double square bracket conditionals

They suffer from the same macro substitution Gotchas as integer operators, but it is far more common as it accrues not only when string in not initialized but also if its length is zero.  "macro substitution Gotchas" lead to run time syntax error. To avoid it the "double quotes" trick is used:  you need to put variables inside double quotes literal, which converts any type to string. Here is the summary of most typical operators used

  • =  is equal to. For example  if [[ "$a" = "$b" ]] ...
  • ==  matches. Pattern is expected as a right side operand and should be "naked" -- without double or single quotes surrounding it (quote string is considered to be a literal and operator became test for string equality). For example  if [[ "$a" == a*b ]] ... Here a*b is a pattern but in if [[ "$a" == "a*b" ]] ... it is a string. Here are additional examples from Advanced Bash guide:
  • [[ $a == z* ]]      # true if $a starts with an "z" (pattern matching)
    [[ "$a" == "z*" ]]  # true if $a is equal to z*
    [ "$a" == z* ]      # file globbing and word splitting take place
    [ "$a" == "z*" ]    # true if $a is equal to z*

  • !=  is not equal to if [[ "$a" != "$b" ]]

    This operator uses pattern matching within a [[ ... ]] construct.

  • < is less than, in ASCII alphabetical order

    if [[ "$a" < "$b" ]]

    if [ "$a" \< "$b" ]

    Note that the "<" needs to be escaped within a [ ] construct.

  • >  is greater than, in ASCII alphabetical order

    if [[ "$a" > "$b" ]]

    if [ "$a" \> "$b" ]

    Note that the ">" needs to be escaped within a [ ] construct.

You can test the string for being null or uninitialized using -z operator (opposite operator is -n  string is not "null".

  •  string is "null", that is, has zero length
  • ! The -z and -n tests absolutely require that the string be quoted within the test brackets.

 For example:

if [ -z "$myvar" ] ; then
        echo "myvar is not defined"
Sometimes, there are several different ways that a particular comparison can be made. For example, the following two snippets of code function identically:
if [[ "$myvar" -eq 3 ] ; then
    echo "myvar equals 3"

if [[ "$myvar" = "3" ] ; then
    echo "myvar equals 3"

In the above two comparisons do exactly the same thing, but the first uses arithmetic comparison operators, while the second uses string comparison operators.

File test operators

The other kind of operator that can be used in conditional expressions checks if a file has certain properties. There are 21 such operators.

The Gotchas is that not all of them are present in all shells so if you run the same script both in bash and ksh you can get into very unpleasant situation. The typical offending test is -e which is present in bash but not in ksh installed by default in major commercial Unixes. Use -f instead: it is more portable way of achieving the same result in all shells.

Here are some operators provided:

  • -a, -e file  file exists
  • -d file file is a directory
  • -f file file is a regular file (i.e., not a directory or other special type of file)
  • -r file (Readable by you) You have read permission on file
  • -s file  (Not empty) file exists and is not empty
  • -w file (Writable by you) You have write permission on file
  • -x file You have execute permission on file, or directory search permission if it is a directory
  • -O file You own file
  • -G file Your group ID is the same as that of file
  • file1 -nt file2 file1 is newer than file2
  • file1 -ot file2 file1 is older than file2
  • -h  file  The file is actually a symbolic link (also -L)
  • -N file Has been modified since last being read
Quoting caveats

Most of the time, while you can omit the use of double quotes surrounding strings and string variables, it's not a good idea. Why? Because your code will work perfectly, unless an environment variable happens to have a space or a tab in it, in which case shell go bananas.  Here's an example of a this Gotchas:

if [ $myvar = "Sep 11, 2010" ] ; then
    echo "yes"

In the above example, if myvar equals "foo", the code will work as expected and will not print anything. However, if myvar equals "Sep 11, 2010", the code will fail with the following error:

[: too many arguments

In this case, the spaces in "$myvar" (which equals "Sep 11, 2010") end up confusing bash. After bash expands "$myvar" using its stupid macro substitution (completely unnecessary in this case)  it ends up with the following comparison:

[ Sep 11, 2010 = "Sep 11, 2010" ]

That's why typically shell string comparisons surround the string arguments with double-quotes.  Here's how the "Sep 11, 2010" comparison should have been written:

if [[ "$myvar" = "Sep 11, 2010" ] ; then
    echo "yes"
Double quotes vs. single quotes quoting of variables

If you want your environment variables to be expanded, you must enclose them in double quotes, rather than single quotes. Single quotes disable variable (as well as history) expansion.

Shell instant arithmetic expressions

You can perform simple integer math using shell constructs. Simply enclose the particular arithmetic expression between a "$((" and a "))", and bash will evaluate the expression. Here are some examples:

$ echo $(( 100 / 3 ))
$ myvar="56"
$ echo $(( $myvar + 12 ))
$ echo $(( $myvar - $myvar ))
$ myvar=$(( $myvar + 1 ))
$ echo $myvar
Compound comparisons and their Gotchas

Compound conditionals are discussed at Compound conditionals

In a compound test, even quoting the string variable might not suffice.

[ -n "$string" -o "$a" = "$b" ]

may cause an error with some versions of Bash if  $string  is empty.  The only way to avoid this is to used stupid trick of appending an extra character inside double quotes, for example:

[[ "x$string" != x -o "x$a" = "x$b" ]]

In case of integer comparisons you can use leading zero

    [[ "o$value" -ne 0 -o "0$a" = "0$b" ]]

Here are logical operations supported by shell:

  • -a  "logical and": exp1 -a exp2  returns true if both exp1 and exp2 are true.
  • -o "logical or":    exp1 -o exp2   returns true if either exp1 or exp2 are true.

These are similar to the Bash comparison operators && and ||, used within .

[[ condition1 && condition2 ]]
The -o and -a operators work with the test command or occur within single test brackets.
if [[ "$exp1" -a "$exp2" ]

The -o operator allows you to test various shell options that may be set using set -o option, returning True (0) if the option is set and False (1) otherwise, as shown in Listing 3.

	Listing 3. Testing shell options
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test -o nounset; echo $?

Finally, the -a and -o options allow you to combine expressions with logical AND and OR, respectively, while the unary ! operator inverts the sense of the test. You may use parentheses to group expressions and override the default precedence. Remember that the shell will normally run an expression between parentheses in a subshell, so you will need to escape the parentheses using \( and \) or enclosing these operators in single or double quotes. Listing 4 illustrates the application of de Morgan's laws to an expression.

	 Combining and grouping tests
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?

(( and [[

The test command is very powerful, but somewhat unwieldy given its requirement for escaping and given the difference between string and arithmetic comparisons. Fortunately, bash has two other ways of testing that are somewhat more natural for people who are familiar with C, C++, or JavaŽ syntax.

The (( )) compound command evaluates an arithmetic expression and sets the exit status to 1 if the expression evaluates to 0, or to 0 if the expression evaluates to a non-zero value. You do not need to escape operators between (( and )). Arithmetic is done on integers. Division by 0 causes an error, but overflow does not. You may perform the usual C language arithmetic, logical, and bitwise operations. The let command can also execute one or more arithmetic expressions. It is usually used to assign values to arithmetic variables.

	 Assigning and testing arithmetic expressions
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13

As with (( )), the [[ ]] compound command allows you to use more natural syntax for filename and string tests. You can combine tests that are allowed for the test command using parentheses and logical operators.

Listing 6. Using the [[ compound
[ian@pinguino ~]$ [[ ( -d "$HOME" ) && ( -w "$HOME" ) ]] &&
> echo "home is a writable directory"
home is a writable directory

The [[ compound can also do pattern matching on strings when the = or != operators are used. The match behaves as for wildcard globbing as illustrated

	 Wildcard tests with [[
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?

You can even do arithmetic tests within [[ compounds, but be careful. Unless within a (( compound, the < and > operators will compare the operands as strings and test their order in the current collating sequence. Listing 8 illustrates this with some examples.

	 Including arithmetic tests with [[
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable


While you could accomplish a huge amount of programming with the above tests and the && and || control operators, bash includes the more familiar "if, then, else" and case constructs. After you learn about these, you will learn about looping constructs and your toolbox will really expand.

If, then, else statements

The bash if command is a compound command that tests the return value of a test or command ($?) and branches based on whether it is True (0) or False (not 0). Although the tests above returned only 0 or 1 values, commands may return other values. Learn more about these in the LPI exam 102 prep: Shells, scripting, programming, and compiling tutorial.

The if command in bash has a then clause containing a list of commands to be executed if the test or command returns 0, one or more optional elif clauses, each with an additional test and then clause with an associated list of commands, an optional final else clause and list of commands to be executed if neither the original test, nor any of the tests used in the elif clauses was true, and a terminal fi to mark the end of the construct.

Using what you have learned so far, you could now build a simple calculator to evaluate arithmetic expressions as shown in Listing 9.

Evaluating expressions with if, then, else
[ian@pinguino ~]$ function mycalc ()

	> {
> local x
> if [ $# -lt 1 ]; then
> echo "This function evaluates arithmetic for you if you give it some"
> elif (( $* )); then
> let x="$*"
> echo "$* = $x"
> else
> echo "$* = 0 or is not an arithmetic expression"
> fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35

The calculator makes use of the local statement to declare x as a local variable that is available only within the scope of the mycalc function. The let function has several possible options, as does the declare function to which it is closely related. Check the man pages for bash, or use help let for more information.

As you saw in Listing 9, you need to make sure that your expressions are properly escaped if they use shell metacharacters such as (, ), *, >, and <. Nevertheless, you have quite a handy little calculator for evaluating arithmetic as the shell does it.

You may have noticed the else clause and the last two examples in Listing 9. As you can see, it is not an error to pass xyz to mycalc, but it evaluates to 0. This function is not smart enough to identify the character values in the final example of use and thus be able to warn the user. You could use a string pattern matching test such as
[[ ! ("$*" == *[a-zA-Z]* ]]
(or the appropriate form for your locale) to eliminate any expression containing alphabetic characters, but that would prevent using hexadecimal notation in your input, since you might use 0x0f to represent 15 using hexadecimal notation. In fact, the shell allows bases up to 64 (using base#value notation), so you could legitimately use any alphabetic character, plus _ and @ in your input. Octal and hexadecimal use the usual notation of a leading 0 for octal and leading 0x or 0X for hexadecimal. Listing 10 shows some examples.

	 Calculating with different bases
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574

Additional laundering of the input is beyond the scope of this tip, so use your calculator with care.

The elif statement is very convenient. It helps you in writing scripts by allowing you to simplify the indenting. You may be surprised to see the output of the type command for the mycalc function as shown in Listing 11.

 Type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
local x;
if [ $# -lt 1 ]; then
echo "This function evaluates arithmetic for you if you give it some";
if (( $* )); then
let x="$*";
echo "$* = $x";
echo "$* = 0 or is not an arithmetic expression";

Of course, you could just do shell arithmetic by using $(( expression )) with the echo command as shown in Listing 12. You wouldn't have learned anything about functions or tests that way, but do note that the shell does not interpret metacharacters, such as *, in their normal role when inside (( expression )) or [[ expression ]].

	 Direct calculation in the shell with echo and $(( ))
[ian@pinguino ~]$ echo $((3 + (4**3 /2)))

If you'd like to know more about Bash scripting in Linux, read the tutorial "LPI exam 102 prep: Shells, scripting, programming, and compiling," from which this article was excerpted, or see the other Resources below. Don't forget to rate this page.

Previous                                                                                                                                                       Next

Back to Top