Chapter 7. Tests

Every reasonably complete programming language can test for a condition, then act according to the result of the test. Bash has the test command, various bracket and parenthesis operators, and the if/then construct.

7.1. Test Constructs


Example 7-1. What is truth?

   1 #!/bin/bash
   2 
   3 #  Tip:
   4 #  If you're unsure of how a certain condition would evaluate,
   5 #+ test it in an if-test.
   6 
   7 echo
   8 
   9 echo "Testing \"0\""
  10 if [ 0 ]      # zero
  11 then
  12   echo "0 is true."
  13 else
  14   echo "0 is false."
  15 fi            # 0 is true.
  16 
  17 echo
  18 
  19 echo "Testing \"1\""
  20 if [ 1 ]      # one
  21 then
  22   echo "1 is true."
  23 else
  24   echo "1 is false."
  25 fi            # 1 is true.
  26 
  27 echo
  28 
  29 echo "Testing \"-1\""
  30 if [ -1 ]     # minus one
  31 then
  32   echo "-1 is true."
  33 else
  34   echo "-1 is false."
  35 fi            # -1 is true.
  36 
  37 echo
  38 
  39 echo "Testing \"NULL\""
  40 if [ ]        # NULL (empty condition)
  41 then
  42   echo "NULL is true."
  43 else
  44   echo "NULL is false."
  45 fi            # NULL is false.
  46 
  47 echo
  48 
  49 echo "Testing \"xyz\""
  50 if [ xyz ]    # string
  51 then
  52   echo "Random string is true."
  53 else
  54   echo "Random string is false."
  55 fi            # Random string is true.
  56 
  57 echo
  58 
  59 echo "Testing \"\$xyz\""
  60 if [ $xyz ]   # Tests if $xyz is null, but...
  61               # it's only an uninitialized variable.
  62 then
  63   echo "Uninitialized variable is true."
  64 else
  65   echo "Uninitialized variable is false."
  66 fi            # Uninitialized variable is false.
  67 
  68 echo
  69 
  70 echo "Testing \"-n \$xyz\""
  71 if [ -n "$xyz" ]            # More pedantically correct.
  72 then
  73   echo "Uninitialized variable is true."
  74 else
  75   echo "Uninitialized variable is false."
  76 fi            # Uninitialized variable is false.
  77 
  78 echo
  79 
  80 
  81 xyz=          # Initialized, but set to null value.
  82 
  83 echo "Testing \"-n \$xyz\""
  84 if [ -n "$xyz" ]
  85 then
  86   echo "Null variable is true."
  87 else
  88   echo "Null variable is false."
  89 fi            # Null variable is false.
  90 
  91 
  92 echo
  93 
  94 
  95 # When is "false" true?
  96 
  97 echo "Testing \"false\""
  98 if [ "false" ]              #  It seems that "false" is just a string.
  99 then
 100   echo "\"false\" is true." #+ and it tests true.
 101 else
 102   echo "\"false\" is false."
 103 fi            # "false" is true.
 104 
 105 echo
 106 
 107 echo "Testing \"\$false\""  # Again, uninitialized variable.
 108 if [ "$false" ]
 109 then
 110   echo "\"\$false\" is true."
 111 else
 112   echo "\"\$false\" is false."
 113 fi            # "$false" is false.
 114               # Now, we get the expected result.
 115 
 116 #  What would happen if we tested the uninitialized variable "$true"?
 117 
 118 echo
 119 
 120 exit 0

Exercise. Explain the behavior of Example 7-1, above.

   1 if [ condition-true ]
   2 then
   3    command 1
   4    command 2
   5    ...
   6 else
   7    # Optional (may be left out if not needed).
   8    # Adds default code block executing if original condition tests false.
   9    command 3
  10    command 4
  11    ...
  12 fi

Note

When if and then are on same line in a condition test, a semicolon must terminate the if statement. Both if and then are keywords. Keywords (or commands) begin statements, and before a new statement on the same line begins, the old one must terminate.

   1 if [ -x "$filename" ]; then

Else if and elif

elif

elif is a contraction for else if. The effect is to nest an inner if/then construct within an outer one.

   1 if [ condition1 ]
   2 then
   3    command1
   4    command2
   5    command3
   6 elif [ condition2 ]
   7 # Same as else if
   8 then
   9    command4
  10    command5
  11 else
  12    default-command
  13 fi

The if test condition-true construct is the exact equivalent of if [ condition-true ]. As it happens, the left bracket, [ , is a token which invokes the test command. The closing right bracket, ] , in an if/test should not therefore be strictly necessary, however newer versions of Bash require it.

Note

The test command is a Bash builtin which tests file types and compares strings. Therefore, in a Bash script, test does not call the external /usr/bin/test binary, which is part of the sh-utils package. Likewise, [ does not call /usr/bin/[, which is linked to /usr/bin/test.

 bash$ type test
 test is a shell builtin
 bash$ type '['
 [ is a shell builtin
 bash$ type '[['
 [[ is a shell keyword
 bash$ type ']]'
 ]] is a shell keyword
 bash$ type ']'
 bash: type: ]: not found
 	      

If, for some reason, you wish to use /usr/bin/test in a Bash script, then specify it by full pathname.


Example 7-2. Equivalence of test, /usr/bin/test, [ ], and /usr/bin/[

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 if test -z "$1"
   6 then
   7   echo "No command-line arguments."
   8 else
   9   echo "First command-line argument is $1."
  10 fi
  11 
  12 echo
  13 
  14 if /usr/bin/test -z "$1"      # Equivalent to "test" builtin.
  15 #  ^^^^^^^^^^^^^              # Specifying full pathname.
  16 then
  17   echo "No command-line arguments."
  18 else
  19   echo "First command-line argument is $1."
  20 fi
  21 
  22 echo
  23 
  24 if [ -z "$1" ]                # Functionally identical to above code blocks.
  25 #   if [ -z "$1"                should work, but...
  26 #+  Bash responds to a missing close-bracket with an error message.
  27 then
  28   echo "No command-line arguments."
  29 else
  30   echo "First command-line argument is $1."
  31 fi
  32 
  33 echo
  34 
  35 
  36 if /usr/bin/[ -z "$1" ]       # Again, functionally identical to above.
  37 # if /usr/bin/[ -z "$1"       # Works, but gives an error message.
  38 #                             # Note:
  39 #                               This has been fixed in Bash, version 3.x.
  40 then
  41   echo "No command-line arguments."
  42 else
  43   echo "First command-line argument is $1."
  44 fi
  45 
  46 echo
  47 
  48 exit 0

The [[ ]] construct is the more versatile Bash version of [ ]. This is the extended test command, adopted from ksh88.

Note

No filename expansion or word splitting takes place between [[ and ]], but there is parameter expansion and command substitution.

   1 file=/etc/passwd
   2 
   3 if [[ -e $file ]]
   4 then
   5   echo "Password file exists."
   6 fi

Tip

Using the [[ ... ]] test construct, rather than [ ... ] can prevent many logic errors in scripts. For example, the &&, ||, <, and > operators work within a [[ ]] test, despite giving an error within a [ ] construct.

Note

Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly necessary.
   1 dir=/home/bozo
   2 
   3 if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
   4   echo "Now in $dir."
   5 else
   6   echo "Can't change to $dir."
   7 fi
The "if COMMAND" construct returns the exit status of COMMAND.

Similarly, a condition within test brackets may stand alone without an if, when used in combination with a list construct.
   1 var1=20
   2 var2=22
   3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
   4 
   5 home=/home/bozo
   6 [ -d "$home" ] || echo "$home directory does not exist."

The (( )) construct expands and evaluates an arithmetic expression. If the expression evaluates as zero, it returns an exit status of 1, or "false". A non-zero expression returns an exit status of 0, or "true". This is in marked contrast to using the test and [ ] constructs previously discussed.


Example 7-3. Arithmetic Tests using (( ))

   1 #!/bin/bash
   2 # Arithmetic tests.
   3 
   4 # The (( ... )) construct evaluates and tests numerical expressions.
   5 # Exit status opposite from [ ... ] construct!
   6 
   7 (( 0 ))
   8 echo "Exit status of \"(( 0 ))\" is $?."         # 1
   9 
  10 (( 1 ))
  11 echo "Exit status of \"(( 1 ))\" is $?."         # 0
  12 
  13 (( 5 > 4 ))                                      # true
  14 echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0
  15 
  16 (( 5 > 9 ))                                      # false
  17 echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1
  18 
  19 (( 5 - 5 ))                                      # 0
  20 echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1
  21 
  22 (( 5 / 4 ))                                      # Division o.k.
  23 echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0
  24 
  25 (( 1 / 2 ))                                      # Division result < 1.
  26 echo "Exit status of \"(( 1 / 2 ))\" is $?."     # Rounded off to 0.
  27                                                  # 1
  28 
  29 (( 1 / 0 )) 2>/dev/null                          # Illegal division by 0.
  30 #           ^^^^^^^^^^^
  31 echo "Exit status of \"(( 1 / 0 ))\" is $?."     # 1
  32 
  33 # What effect does the "2>/dev/null" have?
  34 # What would happen if it were removed?
  35 # Try removing it, then rerunning the script.
  36 
  37 exit 0