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.
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, “What is truth?”, 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
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
[33]
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 testtest is a shell builtinbash$type '['[ is a shell builtinbash$type '[['[[ is a shell keywordbash$type ']]']] is a shell keywordbash$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
[33] 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.