Test Constructs

Advanced Bash-Scripting Guide:
Prev | Chapter 7. Tests | Next

# 7.1. Test Constructs

- An if/then construct tests whether the exit status of a list of commands is 0 (since 0 means "success" by UNIX convention), and if so, executes one or more commands.

- There exists a dedicated command called [ (left bracket special character). It is a synonym for test, and a builtin for efficiency reasons. This command considers its arguments as comparison expressions or file tests and returns an exit status corresponding to the result of the comparison (0 for true, 1 for false).

- With version 2.02, Bash introduced the [[ ... ]] extended test command, which performs comparisons in a manner more familiar to programmers from other languages. Note that [[ is a keyword, not a command.

  Bash sees `[[ $a -lt $b ]]` as a single element, which returns an exit status.

- The (( ... )) and let ... constructs return an exit status, according to whether the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic-expansion constructs may therefore be used to perform arithmetic comparisons.

      (( 0 && 1 ))                 # Logical AND
      echo $?     # 1     ***
      # And so ...
      let "num = (( 0 && 1 ))"
      echo $num   # 0
      # But ...
      let "num = (( 0 && 1 ))"
      echo $?     # 1     ***

      (( 200 || 11 ))              # Logical OR
      echo $?     # 0     ***
      # ...
      let "num = (( 200 || 11 ))"
      echo $num   # 1
      let "num = (( 200 || 11 ))"
      echo $?     # 0     ***

      (( 200 | 11 ))               # Bitwise OR
      echo $?                      # 0     ***
      # ...
      let "num = (( 200 | 11 ))"
      echo $num                    # 203
      let "num = (( 200 | 11 ))"
      echo $?                      # 0     ***

      # The "let" construct returns the same exit status
      #+ as the double-parentheses arithmetic expansion.

  | Again, note that the exit status of an arithmetic expression is not an error value.
      var=-2 && (( var+=2 ))
      echo $?                   # 1

      var=-2 && (( var+=2 )) && echo $var
                                # Will not echo $var!

- An if can test any command, not just conditions enclosed within brackets.

      if cmp a b &> /dev/null  # Suppress output.
      then echo "Files a and b are identical."
      else echo "Files a and b differ."
      fi

      # The very useful "if-grep" construct:
      # -----------------------------------
      if grep -q Bash file
        then echo "File contains at least one occurrence of Bash."
      fi

      word=Linux
      letter_sequence=inu
      if echo "$word" | grep -q "$letter_sequence"
      # The "-q" option to grep suppresses output.
      then
        echo "$letter_sequence found in $word"
      else
        echo "$letter_sequence not found in $word"
      fi

      if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
        then echo "Command succeeded."
        else echo "Command failed."
      fi

- These last two examples courtesy of Stéphane Chazelas.

Example 7-1. What is truth?

    #!/bin/bash

    #  Tip:
    #  If you're unsure how a certain condition might evaluate,
    #+ test it in an if-test.

    echo

    echo "Testing \"0\""
    if [ 0 ]      # zero
    then
      echo "0 is true."
    else          # Or else ...
      echo "0 is false."
    fi            # 0 is true.

    echo

    echo "Testing \"1\""
    if [ 1 ]      # one
    then
      echo "1 is true."
    else
      echo "1 is false."
    fi            # 1 is true.

    echo

    echo "Testing \"-1\""
    if [ -1 ]     # minus one
    then
      echo "-1 is true."
    else
      echo "-1 is false."
    fi            # -1 is true.

    echo

    echo "Testing \"NULL\""
    if [ ]        # NULL (empty condition)
    then
      echo "NULL is true."
    else
      echo "NULL is false."
    fi            # NULL is false.

    echo

    echo "Testing \"xyz\""
    if [ xyz ]    # string
    then
      echo "Random string is true."
    else
      echo "Random string is false."
    fi            # Random string is true.

    echo

    echo "Testing \"\$xyz\""
    if [ $xyz ]   # Tests if $xyz is null, but...
                  # it's only an uninitialized variable.
    then
      echo "Uninitialized variable is true."
    else
      echo "Uninitialized variable is false."
    fi            # Uninitialized variable is false.

    echo

    echo "Testing \"-n \$xyz\""
    if [ -n "$xyz" ]            # More pedantically correct.
    then
      echo "Uninitialized variable is true."
    else
      echo "Uninitialized variable is false."
    fi            # Uninitialized variable is false.

    echo

    xyz=          # Initialized, but set to null value.

    echo "Testing \"-n \$xyz\""
    if [ -n "$xyz" ]
    then
      echo "Null variable is true."
    else
      echo "Null variable is false."
    fi            # Null variable is false.

    echo

    # When is "false" true?

    echo "Testing \"false\""
    if [ "false" ]              #  It seems that "false" is just a string ...
    then
      echo "\"false\" is true." #+ and it tests true.
    else
      echo "\"false\" is false."
    fi            # "false" is true.

    echo

    echo "Testing \"\$false\""  # Again, uninitialized variable.
    if [ "$false" ]
    then
      echo "\"\$false\" is true."
    else
      echo "\"\$false\" is false."
    fi            # "$false" is false.
                  # Now, we get the expected result.

    #  What would happen if we tested the uninitialized variable "$true"?

    echo

    exit 0

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

    if [ condition-true ]
    then
       command 1
       command 2
       ...
    else  # Or else ...
          # Adds default code block executing if original condition tests false.
       command 3
       command 4
       ...
    fi

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

      if [ condition1 ]
      then
         command1
         command2
         command3
      elif [ condition2 ]
      # Same as else if
      then
         command4
         command5
      else
         default-command
      fi

The `if test condition-true` construct is the exact equivalent of `if [ condition-true ]`. As it happens, the left bracket, [ , is a token [1] 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.

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

    #!/bin/bash

    echo

    if test -z "$1"
    then
      echo "No command-line arguments."
    else
      echo "First command-line argument is $1."
    fi

    echo

    if /usr/bin/test -z "$1"      # Equivalent to "test" builtin.
    #  ^^^^^^^^^^^^^              # Specifying full pathname.
    then
      echo "No command-line arguments."
    else
      echo "First command-line argument is $1."
    fi

    echo

    if [ -z "$1" ]                # Functionally identical to above code blocks.
    #   if [ -z "$1"                should work, but...
    #+  Bash responds to a missing close-bracket with an error message.
    then
      echo "No command-line arguments."
    else
      echo "First command-line argument is $1."
    fi

    echo

    if /usr/bin/[ -z "$1" ]       # Again, functionally identical to above.
    # if /usr/bin/[ -z "$1"       # Works, but gives an error message.
    #                             # Note:
    #                               This has been fixed in Bash, version 3.x.
    then
      echo "No command-line arguments."
    else
      echo "First command-line argument is $1."
    fi

    echo

    exit 0

| Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly necessary.
    dir=/home/bozo

    if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
      echo "Now in $dir."
    else
      echo "Can't change to $dir."
    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.
    var1=20
    var2=22
    [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"

    home=/home/bozo
    [ -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 (( ))

    #!/bin/bash
    # arith-tests.sh
    # Arithmetic tests.

    # The (( ... )) construct evaluates and tests numerical expressions.
    # Exit status opposite from [ ... ] construct!

    (( 0 ))
    echo "Exit status of \"(( 0 ))\" is $?."         # 1

    (( 1 ))
    echo "Exit status of \"(( 1 ))\" is $?."         # 0

    (( 5 > 4 ))                                      # true
    echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0

    (( 5 > 9 ))                                      # false
    echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1

    (( 5 == 5 ))                                     # true
    echo "Exit status of \"(( 5 == 5 ))\" is $?."    # 0
    # (( 5 = 5 ))  gives an error message.

    (( 5 - 5 ))                                      # 0
    echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1

    (( 5 / 4 ))                                      # Division o.k.
    echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0

    (( 1 / 2 ))                                      # Division result < 1.
    echo "Exit status of \"(( 1 / 2 ))\" is $?."     # Rounded off to 0.
                                                     # 1

    (( 1 / 0 )) 2>/dev/null                          # Illegal division by 0.
    #           ^^^^^^^^^^^
    echo "Exit status of \"(( 1 / 0 ))\" is $?."     # 1

    # What effect does the "2>/dev/null" have?
    # What would happen if it were removed?
    # Try removing it, then rerunning the script.

    # ======================================= #

    # (( ... )) also useful in an if-then test.

    var1=5
    var2=4

    if (( var1 > var2 ))
    then #^      ^      Note: Not $var1, $var2. Why?
      echo "$var1 is greater than $var2"
    fi     # 5 is greater than 4

    exit 0

### Notes

[1] | A token is a symbol or short string with a special meaning attached to it (a meta-meaning). In Bash, certain tokens, such as [ and . (dot-command), may expand to keywords and commands.

Prev | Home | Next
Tests | Up | File test operators