XXX 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 *** (( 20011 )) # Bitwise OR echo $? # 0 *** # ... let "num = (( 20011 ))" echo $num # 203 let "num = (( 20011 ))" echo $? # 0 *** # The "let" construct returns the same exit status #+ as the double-parentheses arithmetic expansion.
Compte
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 $v
ar!
var=-2 && (( var+=2 )) echo $? # 1 var=-2 && (( var+=2 )) && echo $var # Will not echo $var!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.
Exemple 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
|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.
if [ -x "$filename"
]; then
if [ -x "$filename" ]; then
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 fiThe `` if test condition-true `` construct is the
exact equivalent of `` if [ condition-true ] `` . As it happens, the left bracket, [ , is a token ` [1]
<testconstructs.html#FTN.AEN3140>`__ 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 buil
- tin
- bash$ type ‘[‘ [ is a shell builtin bash$ type ‘[[‘ [[ is a shell keywor
- d
- bash$ type ‘]]’ ]] is a shell keywor
- d
- bash$ type ‘]’ bash: type: ]: not f
ound
If, for some reason, you wish to use `` /usr/bin/test `` in a Bash script, then specify it by full pathname.
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
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
Exemple 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
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.
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.
Arithmetic evaluation of octal / hexadecimal constants takes place automatically within a [[ ... ]] construct.
# [[ Octal and hexadecimal evaluation ]]
# Thank you, Moritz Gronbach, for pointing this out.
decimal=15
octal=017 # = 15 (decimal)
hex=0x0f # = 15 (decimal)
if [ "$decimal" -eq "$octal" ]
then
echo "$decimal equals $octal"
else
echo "$decimal is not equal to $octal" # 15 is not equal to 017
fi # Doesn't evaluate within [ single brackets ]!
if [[ "$decimal" -eq "$octal" ]]
then
echo "$decimal equals $octal" # 15 equals 017
else
echo "$decimal is not equal to $octal"
fi # Evaluates within [[ double brackets ]]!
if [[ "$decimal" -eq "$hex" ]]
then
echo "$decimal equals $hex" # 15 equals 0x0f
else
echo "$decimal is not equal to $hex"
fi # [[ $hexadecimal ]] also evaluates!
file=/etc/passwd
if [[ -e $file ]]
then
echo "Password file exists."
fi
# [[ Octal and hexadecimal evaluation ]]
# Thank you, Moritz Gronbach, for pointing this out.
decimal=15
octal=017 # = 15 (decimal)
hex=0x0f # = 15 (decimal)
if [ "$decimal" -eq "$octal" ]
then
echo "$decimal equals $octal"
else
echo "$decimal is not equal to $octal" # 15 is not equal to 017
fi # Doesn't evaluate within [ single brackets ]!
if [[ "$decimal" -eq "$octal" ]]
then
echo "$decimal equals $octal" # 15 equals 017
else
echo "$decimal is not equal to $octal"
fi # Evaluates within [[ double brackets ]]!
if [[ "$decimal" -eq "$hex" ]]
then
echo "$decimal equals $hex" # 15 equals 0x0f
else
echo "$decimal is not equal to $hex"
fi # [[ $hexadecimal ]] also evaluates!
file=/etc/passwd
if [[ -e $file ]]
then
echo "Password file exists."
fi
# [[ Octal and hexadecimal evaluation ]]
# Thank you, Moritz Gronbach, for pointing this out.
decimal=15
octal=017 # = 15 (decimal)
hex=0x0f # = 15 (decimal)
if [ "$decimal" -eq "$octal" ]
then
echo "$decimal equals $octal"
else
echo "$decimal is not equal to $octal" # 15 is not equal to 017
fi # Doesn't evaluate within [ single brackets ]!
if [[ "$decimal" -eq "$octal" ]]
then
echo "$decimal equals $octal" # 15 equals 017
else
echo "$decimal is not equal to $octal"
fi # Evaluates within [[ double brackets ]]!
if [[ "$decimal" -eq "$hex" ]]
then
echo "$decimal equals $hex" # 15 equals 0x0f
else
echo "$decimal is not equal to $hex"
fi # [[ $hexadecimal ]] also evaluates!
|Note
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/n ull” 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 no t equal to $var2”
home=/home/bozo [ -d “$home” ] || ec
- ho “$home directory does
- not exist.”
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
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."
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
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 <dblparens.html>`__ 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.
Exemple 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] <testconstructs.html#AEN3140>`__
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.