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.
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 # The very useful "if-grep" construct: 7 # ----------------------------------- 8 if grep -q Bash file 9 then echo "File contains at least one occurrence of Bash." 10 fi 11 12 word=Linux 13 letter_sequence=inu 14 if echo "$word" | grep -q "$letter_sequence" 15 # The "-q" option to grep suppresses output. 16 then 17 echo "$letter_sequence found in $word" 18 else 19 echo "$letter_sequence not found in $word" 20 fi 21 22 23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED 24 then echo "Command succeeded." 25 else echo "Command failed." 26 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 Stéphane Chazelas.
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 |
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.
|
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.
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.
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.
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 |
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. |
Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly necessary.
Similarly, a condition within test brackets may stand alone without an if, when used in combination with a list construct.
|
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 |