|Advanced Bash-Scripting Guide: A complete guide to shell scripting, using Bash|
|Prev||Chapter 7. Tests||Next|
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.
let "1<2" returns 0 (as "1<2" expands to "1") (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")
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 if grep -q Bash file then echo "File contains at least one occurrence of Bash." fi if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED then echo "Command succeeded." else echo "Command failed." fi
An if/then construct can contain nested comparisons and tests.
if echo "Next *if* is part of the comparison for the first *if*." if [[ $comparison = "integer" ]] then (( a < b )) else [[ $a < $b ]] fi then echo '$a is less than $b' fi
This detailed "if-test" explanation courtesy of Stephane Chazelas.
Example 7-1. What is truth?
#!/bin/bash echo echo "Testing \"0\"" if [ 0 ] # zero then echo "0 is true." else echo "0 is false." fi echo echo "Testing \"NULL\"" if [ ] # NULL (empty condition) then echo "NULL is true." else echo "NULL is false." fi echo echo "Testing \"xyz\"" if [ xyz ] # string then echo "Random string is true." else echo "Random string is false." fi 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 echo echo "Testing \"-n \$xyz\"" if [ -n "$xyz" ] # More pedantically correct. then echo "Uninitialized variable is true." else echo "Uninitialized variable is false." fi 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 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 echo echo "Testing \"\$false\"" # Again, uninitialized variable. if [ "$false" ] then echo "\"\$false\" is true." else echo "\"\$false\" is false." fi # Now, we get the expected result. echo exit 0
Exercise. Explain the behavior of Example 7-1, above.
if [ condition-true ] then command 1 command 2 ... else # Optional (may be left out if not needed). # Adds default code block executing if original condition tests false. command 3 command 4 ... fi
Add a semicolon when 'if' and 'then' are on same line.
if [ -x "$filename" ]; then
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 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.
Example 7-2. Equivalence of [ ] and test
#!/bin/bash echo if test -z "$1" then echo "No command-line arguments." else echo "First command-line argument is $1." fi if [ -z "$1" ] # Functionally identical to above code block. # 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 exit 0
No filename expansion or word splitting takes place between [[ and ]], but there is parameter expansion and command substitution.
file=/etc/passwd if [[ -e $file ]] then echo "Password file exists." 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 (( ))
#!/bin/bash # 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 $? # 0 (( 5 > 9 )) # false echo $? # 1 exit 0