Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
PrevNext

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

  • 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 also return an exit status of 0 if the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic expansion constructs may therefore be used to perform arithmetic comparisons.
     1 let "1<2" returns 0 (as "1<2" expands to "1")
     2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

  • An if can test any command, not just conditions enclosed within brackets.
     1 if cmp a b &> /dev/null # Suppress output.
     2 then echo "Files a and b are identical."
     3 else echo "Files a and b differ."
     4 fi
     5 
     6 if grep -q Bash file
     7 then echo "File contains at least one occurrence of Bash."
     8 fi
     9 
     10 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
     11 then echo "Command succeeded."
     12 else echo "Command failed."
     13 fi

  • An if/then construct can contain nested comparisons and tests.
     1 if echo "Next *if* is part of the comparison for the first *if*."
     2 
     3 if [[ $comparison = "integer" ]]
     4 then (( a < b ))
     5 else
     6 [[ $a < $b ]]
     7 fi
     8 
     9 then
     10 echo '$a is less than $b'
     11 fi

    This detailed "if-test" explanation courtesy of Stephane Chazelas.


Example 7-1. What is truth?

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


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ドル" # Same result as "test" builtin".
 15 then
 16 echo "No command-line arguments."
 17 else
 18 echo "First command-line argument is 1ドル."
 19 fi
 20 
 21 echo
 22 
 23 if [ -z "1ドル" ] # Functionally identical to above code blocks.
 24 # if [ -z "1ドル" should work, but...
 25 #+ Bash responds to a missing close-bracket with an error message.
 26 then
 27 echo "No command-line arguments."
 28 else
 29 echo "First command-line argument is 1ドル."
 30 fi
 31 
 32 echo
 33 
 34 if /usr/bin/[ -z "1ドル" # Again, functionally identical to above.
 35 # if /usr/bin/[ -z "1ドル" ] # Works, but gives an error message.
 36 then
 37 echo "No command-line arguments."
 38 else
 39 echo "First command-line argument is 1ドル."
 40 fi
 41 
 42 echo
 43 
 44 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 echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1
 31 
 32 # What effect does the "2>/dev/null" have?
 33 # What would happen if it were removed?
 34 # Try removing it, then rerunning the script.
 35 
 36 exit 0


PrevHomeNext
Exit and Exit StatusUpFile test operators

AltStyle によって変換されたページ (->オリジナル) /