14.5. Conditional Constructs and Flow Control
14.5.1 Exit Status
Conditional commands allow you to perform some task(s) based on whether a condition succeeds or fails. The if command is the simplest form of decision making; the if/else commands allow a two-way decision; and the if/elif/else commands allow a multiway decision.
Bash allows you to test two types of conditions: the success or failure of commands or whether an expression is true or false. In either case, the exit status is always used. An exit status of 0 indicates success or true, and an exit status that is nonzero indicates failure or false. The ? status variable contains a numeric value representing the exit status. To refresh your memory on how exit status works, look at Example 14.15.
Example 14.15.
(At the Command Line)
1 $ name=Tom
2 $ grep "$name" /etc/passwd
Tom:8ZKX2F:5102:40:Tom Savage:/home/tom:/bin/sh
3 $ echo $?
0 # Success!
4 $ name=Fred
5 $ grep "$name" /etc/passwd
$ echo $?
1 # Failure
EXPLANATION
The variable name is assigned the string Tom. The grep command will search for string Tom in the passwd file. The ? variable contains the exit status of the last command executed; in this case, the exit status of grep. If grep is successful in finding the string Tom, it will return an exit status of 0. The grep command was successful. The variable name is assigned Fred. The grep command searches for Fred in the passwd file and is unable to find him. The ? variable has a value of 1, indicating that grep failed.
14.5.2 The Built-In test and let Commands
The test Command with Single Brackets
To evaluate an expression, the built-in test command is commonly used. This command is also linked to the bracket symbol. Either the test command itself can be used, or the expression can be enclosed in a set of single brackets. Shell metacharacter expansion is not performed on expressions evaluated with the simple test command or when square brackets are used. Because word splitting is performed on variables, strings containing whitespace must be quoted. (See Example 14.16.)
The test Command with Double Brackets
On versions of bash 2.x, double brackets [[ ]] (the built-in compound test command) can be used to evaluate expressions. Word splitting is not performed on variables and pattern matching is done, allowing the expansion of metacharacters. A literal string containing whitespace must be quoted and if a string (with or without whitespace) is to be evaluated as an exact string, rather than part of a pattern, it too must be enclosed in quotes. The logical operators && (AND) and || (OR) replace the –a and –o operators used with the simple test command. (See Example 14.17.)
Example 14.16.
(The test Command)
(At the Command Line)
1 $ name=Tom
2 $ grep "$name" /etc/passwd
3 $ echo $?
4 $ test $name != Tom
5 $ echo $?
1 # Failure
6 $ [ $name = Tom ] # Brackets replace the test command
7 $ echo $?
0
8 $ [ $name = [Tt]?? ]
$ echo $?
1
9 $ x=5
$ y=20
10 $ [ $x -gt $y ]
$ echo $?
1
11 $ [ $x -le $y ]
$ echo $?
0
EXPLANATION
The variable name is assigned the string Tom. The grep command will search for string Tom in the passwd file. The ? variable contains the exit status of the last command executed, in this case, the exit status of grep. If grep is successful in finding the string Tom, it will return an exit status of 0. The grep command was successful. The test command is used to evaluate strings, numbers, and perform file testing. Like all commands, it returns an exit status. If the exit status is 0, the expression is true; if the exit status is 1, the expression evaluates to false. There must be spaces surrounding the equal sign. The value of name is tested to see if it is not equal to Tom. The test fails and returns an exit status of 1. The brackets are an alternate notation for the test command. There must be spaces after the first bracket. The expression is tested to see if name evaluates to the string Tom. Bash allows either a single or double equal sign to be used to test for equality of strings. The exit status of the test is 0. The test was successful because name is equal to Tom. The test command does not allow wildcard expansion. Because the question mark is treated as a literal character, the test fails. Tom and [Tt]?? are not equal. The exit status is 1, indicating that the text in line 8 failed. x and y are given numeric values. The test command uses numeric relational operators to test its operands; in this example it tests whether $x is greater than (–gt) $y, and returns 0 exit status if true, 1 if false. (See Table 14.3 on page 881.) Table 14.3. The test Command OperatorsTest Operator | Tests True If |
---|
String Test |
[ string1 = string2 ]
[ string1==string2 ]
| String1 is equal to String2 (space surrounding = required).
(Can be used instead of the single = sign on bash versions 2.x.) | [ string1 != string2 ] | String1 is not equal to String2 (space surrounding != required). | [ string ] | String is not null. | [ –z string ] | Length of string is zero. | [ –n string ] | Length of string is nonzero. | [ –l string ] | Length of string (number of characters). | | EXAMPLE
test –n $word or [ –n $word ] test tom = sue or [ tom = sue ]
| Logical Test | [ string1 –a string1 ] | Both string1 and string2 are true. | [ string1 –o string2 ] | Either string1 or string2 is true. | [ ! string1 ] | Not a string1 match. | Logical Test (Compound Test) | [[ pattern1 && pattern2 ]] | Both pattern1 and pattern2 are true. | [[ pattern1 || pattern2 ]] | Either pattern1 or pattern2 is true. | [[ ! pattern ]] | Not a pattern match. | Integer Test | [ int1 –eq int2 ] | Int1 is equal to int2. | [ int1 –ne int2 ] | Int1 is not equal to int2. | [ int1 –gt int2 ] | Int1 is greater than int2. | [ int1 –ge int2 ] | Int1 is greater than or equal to int2. | [ int1 –lt int2 ] | Int1 is less than int2. | [ int1 –le int2 ] | Int1 is less than or equal to int2. | Binary Operators for File Testing | [ file1 –nt file2 ] | True if file1 is newer than file2 (according to modification date). | [ file1 –ot file2 ] | True if file1 is older than file2. | [ file1 –ef file2 ] | True if file1and file2 have the same device or inode numbers. |
Tests if $x less than or equal to (–le) $y, returning 0 exit status if true, 1 if false.
Example 14.17.
(The compound test command)(bash 2.x)
$ name=Tom; friend=Joseph
1 $ [[ $name == [Tt]om ]] # Wildcards allowed
$ echo $?
0
2 $ [[ $name == [Tt]om && $friend == "Jose" ]]
$ echo $?
1
3 $ shopt -s extglob # Turns on extended pattern matching
4 $ name=Tommy
5 $ [[ $name == [Tt]o+(m)y ]]
$ echo $?
0
EXPLANATION
If using the compound test command, shell metacharacters can be used in string tests. In this example, the expression is tested for string equality where name can match either Tom, tom, tommy , and so forth. If the expression is true, the exit status (?) is 0. The logical operators && (AND) and || (OR) can be used with the compound test. If && is used, both expressions must be true, and if the first expression evaluates as false, no further checking is done. With the || logical operator, only one of the expressions must be true. If the first expression evaluates true, no further checking is done. Note that "Jose" is quoted. If not quoted, the friend variable would be checked to see if it contained the pattern Jose. Jose would match, and so would Joseph. The expression evaluates to false because the second condition is not true. The exit status is 1. Extended pattern matching is turned on with the built-in shopt command. The variable is assigned the value Tommy. In this test, the expression is tested for string equality using the new pattern-matching metacharacters. It tests if name matches a string starting with T or t, followed by an o, one or more m characters, and a y.
The following examples illustrate how the exit status is tested with the built-in test command and the alternate form of test, a set of single brackets [ ]; the compound command, a set of double brackets [[ ]].
The let Command and Arithmetic with Double Parentheses
Although the test command can evaluate arithmetic expressions, you may prefer to use the let command with its rich set of C-like operators (bash 2.x). The let command can be represented alternatively by enclosing its expression in a set of double parentheses.
Whether you are using the test command, compound command, or let command, the result of an expression is tested, with zero status indicating success and nonzero status indicating failure. (See Table 14.4.)
Example 14.18 illustrates how the let command uses double parentheses (( )).
Example 14.18.
(The let Command) (bash 2.x)
(At the Command Line)
1 $ x=2
$ y=3
2 (( x > 2 ))
echo $?
1
3 (( x < 2 ))
echo $?
0
4 (( x == 2 && y == 3 ))
echo $?
0
5 (( x > 2 || y < 3 ))
echo $?
1
EXPLANATION
x and y are assigned numeric values. The double parentheses replace the let command to evaluate the numeric expression. If x is greater than y , the exit status is 0. Because the condition is not true, the exit status is 1. The ? variable holds the exit status of the last command executed—the (( )) command. Note: To evaluate a variable, the dollar sign is not necessary when the variable is enclosed in (( )) The double parentheses evaluate the expression. If x is less than 2, an exit status of 0 is returned; otherwise, 1 is returned. The compound expression is evaluated. The expression is tested as follows: if x is equal to 2 AND y is equal to 3 (i.e., both expressions are true), then an exit status of 0 is returned; otherwise, 1 is returned. The compound expression is evaluated. The expression is tested as follows: if x is greater than 2 OR y is less than 3 (i.e., one of the expressions is true), then an exit status of 0 is returned; otherwise, 1 is returned.
14.5.3 The if Command
The simplest form of conditional is the if command. The command (a bash built-in or executable) following the if construct is executed and its exit status is returned. The exit status is usually determined by the programmer who wrote the utility. If the exit status is 0, the command succeeded and the statement(s) after the then keyword are executed. In the C shell, the expression following the if command is a Boolean-type expression as in C. But in the Bash, Bourne, and Korn shells, the statement following the if is a command or group of commands. If the exit status of the command being evaluated is 0, the block of statements after the then is executed until fi is reached. The fi terminates the if block. If the exit status is nonzero, meaning that the command failed in some way, the statement(s) after the then keyword are ignored and control goes to the line directly after the fi statement.
It is important that you know the exit status of the commands being tested. For example, the exit status of grep is reliable in letting you know whether grep found the pattern it was searching for in a file. If grep is successful in its search, it returns a 0 exit status; if not, it returns 1. The sed and awk programs also search for patterns, but they will report a successful exit status regardless of whether they find the pattern. The criterion for success with sed and awk is correct syntax, not functionality.
FORMAT
if command
then
command
command
fi
------------------------------------
(Using test for numbers and strings -- old format)
if test expression
then
command
fi
or
if [ string/numeric expression ] then
command
fi
-------------------------------------
(Using test for strings -- new format)
if [[ string expression ]] then
command
fi
(Using let for numbers -- new format)
if (( numeric expression ))
--------------------------------------
Example 14.19.
1 if grep "$name" /etc/passwd > /dev/null 2>&1
2 then
echo Found $name!
3 fi
EXPLANATION
The grep command searches for its argument, name, in the /etc/passwd database. Standard output and standard error are redirected to /dev/null, the UNIX bit bucket. If the exit status of the grep command is zero, the program goes to the then statement and executes commands until fi is reached. Indentation of commands between the then and fi keywords is a convention used to make the program more readable, and hence, easier to debug. The fi terminates the list of commands following the then statement.
Example 14.20.
1 echo "Are you o.k. (y/n) ?"
read answer
2 if [ "$answer" = Y -o "$answer" = y ]
then
echo "Glad to hear it."
3 fi
4 if [ $answer = Y -o "$answer" = y ]
[: too many arguments
-------------------------------------------
5 if [[ $answer == [Yy]* || $answer == Maybe ]]
then
echo "Glad to hear it."
fi
6 shopt -s extglob
7 answer="not really"
8 if [[ $answer = [Nn]o?( way|t really) ]]
then
echo "So sorry. "
fi
EXPLANATION
The user is asked the question and told to respond. The read command waits for a response. The test command, represented by square brackets, is used to test expressions. It returns an exit status of zero if the expression is true and nonzero if the expression is false. If the variable answer evaluates to Y or y, the commands after the then statement are executed. (The test command does not allow the use of wildcards when testing expressions, and spaces must surround the square brackets, as well as the = operators. See Table 14.3.) Quoting $answer assures that its value is a single string. The test command will fail if more than one word is tested. The fi terminates the if on line 2. The test command fails if more than one word appears before the = operator. For example, if the user entered yes, you betcha, the answer variable would evaluate to three words, causing the test to fail. $answer is enclosed in double quotes to prevent the resulting error message shown here. The compound command operators [[ ]] allow the expansion of shell metacharacters in a string expression. The variable does not need quotes surrounding it, even if it contains more than one word, as it did with the old test command. (The double equal sign can be used to replace the single equal sign.) The shopt built-in, if set to extglob, allows expanded parameter expansion. See Table 14.11 on page 955. Table 14.11. The shopt Command OptionsOption | Meaning |
---|
cdable_vars | If an argument to the cd built-in command is not a directory, it is assumed to be the name of a variable whose value is the directory to change to. | cdspell | Corrects minor errors in the spelling of a directory name in a cd command. The errors checked for are transposed characters, a missing character, and a character too many. If a correction is found, the corrected path is printed, and the command proceeds. Only used by interactive shells. | checkhash | Bash checks that a command found in the hash table exists before trying to execute it. If a hashed command no longer exists, a normal path search is performed. | checkwinsize | Bash checks the window size after each command and, if necessary, updates the values of LINES and COLUMNS. | cmdhist | Bash attempts to save all lines of a multiple-line command in the same history entry. This allows easy re-editing of multiline commands. | dotglob | Bash includes filenames beginning with a dot (.) in the results of filename expansion. | execfail | A noninteractive shell will not exit if it cannot execute the file specified as an argument to the exec built-in command. An interactive shell does not exit if exec fails. | expand_aliases | Aliases are expanded. Enabled by default. | extglob | The extended pattern matching features (regular expression metacharacters derived from Korn shell for filename expansion) are enabled. | histappend | The history list is appended to the file named by the value of the HISTFILE variable when the shell exits, rather than overwriting the file. | histreedit | If readline is being used, a user is given the opportunity to re-edit a failed history substitution. | histverify | If set, and readline is being used, the results of history substitution are not immediately passed to the shell parser. Instead, the resulting line is loaded into the readline editing buffer, allowing further modification. | hostcomplete | If set, and readline is being used, bash will attempt to perform hostname completion when a word containing an @ is being completed. Enabled by default. | huponexit | If set, bash will send SIGHUP (hangup signal) to all jobs when an interactive login shell exits. | interactive_comments | Allows a word beginning with # to cause that word and all remaining characters on that line to be ignored in an interactive shell. Enabled by default. | lithist | If enabled, and the cmdhist option is enabled, multiline commands are saved to the history with embedded newlines rather than using semicolon separators where possible. | mailwarn | If set, and a file that bash is checking for mail has been accessed since the last time it was checked, the message The mail in mailfile has been read is displayed. | nocaseglob | If set, bash matches filenames in a case-insensitive fashion when performing filename expansion. | nullglob | If set, bash allows filename patterns that match no files to expand to a null string, rather than themselves. | promptvars | If set, prompt strings undergo variable and parameter expansion after being expanded. Enabled by default. | restricted_shell | The shell sets this option if it is started in restricted mode. The value may not be changed. This is not reset when the startup files are executed, allowing the startup files to discover whether or not a shell is restricted. | shift_verbose | If this is set, the shift built-in prints an error message when the shift count exceeds the number of positional parameters. | sourcepath | If set, the source built-in uses the value of PATH to find the directory containing the file supplied as an argument. Enabled by default. | source | A synonym for dot (.). |
The answer variable is set to the string "not really". Extended pattern matching is used here. The expression reads: If the value of the answer variable matches a string starting with no or No and if followed by zero or one occurrences of the expression in the parentheses, the expression is true. The expression could be evaluated to no, No, no way, No way, not really, or Not really.
The exit Command and the ? Variable
The exit command is used to terminate the script and return to the command line. You may want the script to exit if some condition occurs. The argument given to the exit command is a number ranging from 0 to 255. If the program exits with 0 as an argument, the program exited with success. A nonzero argument indicates some kind of failure. The argument given to the exit command is stored in the shell's ? variable.
Example 14.21.
(The Script)
$ cat bigfiles
# Name: bigfiles
# Purpose: Use the find command to find any files in the root
# partition that have not been modified within the past n (any
# number within 30 days) days and are larger than 20 blocks
# (512-byte blocks)
1 if (( $# != 2 )) # [ $# -ne 2 ]
then
echo "Usage: $0 mdays size " 1>&2
exit 1
2 fi
3 if (( $1 < 0 || $1 > 30 )) # [ $1 -lt 0 -o $1 -gt 30 ]
then
echo "mdays is out of range"
exit 2
4 fi
5 if (( $2 <= 20 )) # [ $2 -le 20 ]
then
echo "size is out of range"
exit 3
6 fi
7 find / -xdev -mtime $1 -size +$2
(The Command Line)
$ bigfiles
Usage: bigfiles mdays size
$ echo $?
1
$ bigfiles 400 80
mdays is out of range
$ echo $?
2
$ bigfiles 25 2
size is out of range
$ echo $?
3
$ bigfiles 2 25
(Output of find prints here)
EXPLANATION
The statement reads: If the number of arguments is not equal to 2, print the error message and send it to standard error, then exit the script with an exit status of 1. Either the built-in test command or the let command can be used to test numeric expressions. The fi marks the end of the block of statements after then. The statement reads: If the value of the first positional parameter passed in from the command line is less than 0 or greater than 30, then print the message and exit with a status of 2. See Table 14.4 on page 884 for numeric operators. The fi ends the if block. The statement reads: If the value of the second positional parameter passed in at the command line is less than or equal to 20 (512-byte blocks), then print the message and exit with a status of 3. The fi ends the if block. The find command starts its search in the root directory. The –xdev option prevents find from searching other partitions. The –mtime option takes a number argument, which is the number of days since the file was modified, and the –size option takes a number argument, which is the size of the file in 512-byte blocks.
Checking for Null Values
When checking for null values in a variable, use double quotes to hold the null value or the test command will fail.
Example 14.22.
(The Script)
1 if [ "$name" = "" ] # Alternative to [ ! "$name" ] or [ -z "$name" ]
then
echo The name variable is null
fi
(From System showmount program, which displays all remotely mounted systems)
2 remotes=$(/usr/sbin/showmount)
if [ "X${remotes}" != "X" ]
then
/usr/sbin/wall ${remotes}
...
3 fi
EXPLANATION
If the name variable evaluates to null, the test is true. The double quotes are used to represent null. The showmount command lists all clients remotely mounted from a host machine. The command will list either one or more clients, or nothing. The variable remotes will either have a value assigned or will be null. The letter X precedes the variable remotes when being tested. If remotes evaluates to null, no clients are remotely logged on and X will be equal to X, causing the program to start execution again on line 3. If the variable has a value, for example, the hostname pluto, the expression would read if Xpluto != X, and the wall command would be executed. (All users on remote machines will be sent a message.) The purpose of using X in the expression is to guarantee that even if the value of remotes is null, there will always be a placeholder on either side of the != operator in the expression. The fi terminates the if.
Nested if Commands
When if statements are nested, the fi statement always goes with the nearest if statement. Indenting the nested ifs makes it easier to see which if statement goes with which fi statement.
14.5.4 The if/else Command
The if/else commands allow a two-way decision-making process. If the command after the if fails, the commands after the else are executed.
FORMAT
if command
then
command(s)
else
command(s)
fi
Example 14.23.
(The Script)
#!/bin/bash
# Scriptname: grepit
1 if grep "$name" /etc/passwd >& /dev/null; then
2 echo Found $name!
3 else
4 echo "Can't find $name."
exit 1
5 fi
EXPLANATION
The grep command searches for its argument, name, in the NIS passwd database. Because the user does not need to see the output, standard output and standard error are redirected to /dev/null, the UNIX/Linux bit bucket. If the exit status of the grep command is 0, program control goes to the then statement and executes commands until else is reached. The commands under the else statement are executed if the grep command fails to find $name in the passwd database; that is, the exit status of grep must be nonzero for the commands in the else block to be executed. If the value in $name is not found in the passwd database, this echo statement is executed and the program exits with a value of 1, indicating failure. The fi terminates the if.
Example 14.24.
(The Script)
#!/bin/bash
# Scriptname: idcheck
# purpose:check user id to see if user is root.
# Only root has a uid of 0.
# Format for id output:uid=9496(ellie) gid=40 groups=40
# root's uid=0
1 id=`id | gawk –F'[=(]' '{print $2}'` # get user id
echo your user id is: $id
2 if (( id == 0 )) # [
$id –eq 0 ] (See cd file: idcheck2)
then
3 echo "you are superuser."
4 else
echo "you are not superuser."
5 fi
(The Command Line)
6 $ idcheck
your user id is: 9496
you are not superuser.
7 $ su
Password:
8 # idcheck
your user id is: 0
you are superuser
EXPLANATION
1 The id command is piped to the gawk command. Gawk uses an equal sign and open parenthesis as field separators, extracts the user ID from the output, and assigns the output to the variable id. 2,3,4 If the value of id is equal to 0, then line 3 is executed. If id is not equal to 0, the else statements are executed. 5 The fi marks the end of the if command. 6 The idcheck script is executed by the current user, whose UID is 9496. 7 The su command switches the user to root. 8 The # prompt indicates that the superuser (root) is the new user. The UID for root is 0.
14.5.5 The if/elif/else Command
The if/elif/else commands allow a multiway decision-making process. If the command following the if fails, the command following the elif is tested. If that command succeeds, the commands under its then statement are executed. If the command after the elif fails, the next elif command is checked. If none of the commands succeeds, the else commands are executed. The else block is called the default.
FORMAT
if command
then
command(s)
elif command
then
commands(s)
elif command
then
command(s)
else
command(s)
fi
Example 14.25.
(The Script)
#!/bin/bash
# Scriptname: tellme
# Using the old-style test command
1 echo -n "How old are you? "
read age
2 if [ $age -lt 0 -o $age -gt 120 ]
then
echo "Welcome to our planet! "
exit 1
fi
3 if [ $age -ge 0 -a $age -le 12 ]
then
echo "A child is a garden of verses"
elif [ $age -gt 12 -a $age -le 19 ]
then
echo "Rebel without a cause"
elif [ $age -gt 19 -a $age -le 29 ]
then
echo "You got the world by the tail!!"
elif [ $age -gt 29 -a $age -le 39 ]
then
echo "Thirty something..."
4 else
echo "Sorry I asked"
5 fi
(The Output)
$ tellme
How old are you? 200
Welcome to our planet!
$ tellme
How old are you? 13
Rebel without a cause
$ tellme
How old are you? 55
Sorry I asked
----------------------------------------------------------
#!/bin/bash
# Using the new (( )) compound let command
# Scriptname: tellme2
1 echo -n "How old are you? "
read age
2 if (( age < 0 || age > 120 ))
then
echo "Welcome to our planet! "
exit 1
fi
3 if ((age >= 0 && age <= 12))
then
echo "A child is a garden of verses"
elif ((age > 12 && age <= 19 ))
then
echo "Rebel without a cause"
elif (( age > 19 && age <= 29 ))
then
echo "You got the world by the tail!!"
elif (( age > 29 && age <= 39 ))
then
echo "Thirty something..."
4 else
echo "Sorry I asked"
5 fi
EXPLANATION
The user is asked for input. The input is assigned to the variable age. A numeric test is performed by the test command. If age is less than 0 or greater than 120, the echo command is executed and the program terminates with an exit status of 1. The interactive shell prompt will appear. A numeric test is performed by the test command. If age is greater than or equal to 0 and less than or equal to 12, the test command returns exit status 0, true, and the statement after the then is executed. Otherwise, program control goes to the elif. If that test is false, the next elif is tested. The else construct is the default. If none of the above statements are true, the else commands will be executed. The fi terminates the initial if statement.
14.5.6 File Testing
Often when you are writing scripts, your script will require that there are certain files available and that those files have specific permissions, are of a certain type, or have other attributes. You will find file testing a necessary part of writing dependable scripts.
Example 14.26.
(The Script)
#!/bin/bash
# Using the old-style test command [ ] single brackets
# filename: perm_check
file=./testing
1 if [ -d $file ]
then
echo "$file is a directory"
2 elif [ -f $file ]
then
3 if [ -r $file -a -w $file -a -x $file ]
then # nested if command
echo "You have read,write,and execute permission on $file."
4 fi
5 else
echo "$file is neither a file nor a directory. "
6 fi
----------------------------------------------------------
#!/bin/bash
# Using the new compound operator for test [[ ]]
# filename: perm_check2
file=./testing
1 if [[ -d $file ]]
then
echo "$file is a directory"
2 elif [[ -f $file ]]
then
3 if [[ -r $file && -w $file && -x $file ]]
then # nested if command
echo "You have read,write,and execute permission on $file."
4 fi
5 else
echo "$file is neither a file nor a directory. "
6 fi
Table 14.5. File-Testing OperatorsTest Operator | Tests True If |
---|
–b filename | Block special file | –c filename | Character special file | –d filename | Directory existence | –e filename | File existence | –f filename | Regular file existence and not a directory | –G filename | True if file exists and is owned by the effective group ID | –g filename | Set-group-ID is set | –k filename | Sticky bit is set | –L filename | File is a symbolic link | –p filename | File is a named pipe | –O filename | File exists and is owned by the effective user ID | –r filename | File is readable | –S filename | File is a socket | –s filename | File is nonzero size | –t fd | True if fd (file descriptor) is opened on a terminal | –u filename | Set-user-ID bit is set | –w filename | File is writable | –x filename | File is executable |
EXPLANATION
If the file testing is a directory, print testing is a directory. If the file testing is not a directory, else if the file is a plain file, then . . . If the file testing is readable and writable, and executable, then . . . The fi terminates the innermost if command. The else commands are executed if lines 1 and 2 are not true. This fi goes with the first if.
14.5.7 The null Command
The null command, represented by a colon, is a built-in, do-nothing command that returns an exit status of 0. It is used as a placeholder after an if command when you have nothing to say, but need a command or the program will produce an error message because a command is required after the then statement. Often the null command is used as an argument to a loop command to make the loop a forever loop.
Example 14.27.
(The Script)
#!/bin/bash
# filename: name_grep
1 name=Tom
2 if grep "$name" databasefile >& /dev/null
then
3 :
4 else
echo "$1 not found in databasefile"
exit 1
fi
EXPLANATION
The variable name is assigned the string Tom. The if command tests the exit status of the grep command. If Tom is found in databasefile, the null command (a colon) is executed and does nothing. Both output and errors are redirected to /dev/null. The colon is the null command. It does nothing other than returning a 0 exit status. What we really want to do is print an error message and exit if Tom is not found. The commands after the else will be executed if the grep command fails.
Example 14.28.
(The Command Line)
1 $ DATAFILE=
2 $ : ${DATAFILE:=$HOME/db/datafile}
$ echo $DATAFILE
/home/jody/ellie/db/datafile
3 $ : ${DATAFILE:=$HOME/junk}
$ echo $DATAFILE
/home/jody/ellie/db/datafile
EXPLANATION
The variable DATAFILE is assigned null. The colon command is a do-nothing command. The modifier (:=) returns a value that can be assigned to a variable or used in a test. In this example, the expression is passed as an argument to the do-nothing command. The shell will perform variable substitution; that is, assign the pathname to DATAFILE if DATAFILE does not already have a value. The variable DATAFILE is permanently set. Because the variable has already been set, it will not be reset with the default value provided on the right of the modifier.
Example 14.29.
(The Script)
#!/bin/bash
# Scriptname: wholenum
# Purpose:The expr command tests that the user enters an integer
1 echo "Enter an integer."
read number
2 if expr "$number" + 0 >& /dev/null
then
3 :
else
4 echo "You did not enter an integer value."
exit 1
5 fi
EXPLANATION
The user is asked to enter an integer. The number is assigned to the variable number. The expr command evaluates the expression. If addition can be performed, the number is a whole number and expr returns a successful exit status. All output is redirected to the bit bucket /dev/null. If expr is successful, it returns a 0 exit status, and the colon command does nothing. If the expr command fails, it returns a nonzero exit status, the echo command displays the message, and the program exits. The fi ends the if block.
14.5.8 The case Command
The case command is a multiway branching command used as an alternative to if/elif commands. The value of the case variable is matched against value1, value2, and so forth, until a match is found. When a value matches the case variable, the commands following the value are executed until the double semicolons are reached. Then execution starts after the word esac (case spelled backward).
If the case variable is not matched, the program executes the commands after the *), the default value, until ;; or esac is reached. The *) value functions the same as the else statement in if/else conditionals. The case values allow the use of shell wildcards and the vertical bar (pipe symbol) for ORing two values.
FORMAT
case variable in
value1)
command(s)
;;
value2)
command(s)
;;
*)
command(s)
;;
esac
Example 14.30.
(The Script)
#!/bin/bash
# Scriptname: xcolors
1 echo -n "Choose a foreground color for your xterm window: "
read color
2 case "$color" in
3 [Bb]l??)
4 xterm -fg blue -fn terminal &
5 ;;
6 [Gg]ree*)
xterm -fg darkgreen -fn terminal &
;;
7 red | orange) # The vertical bar means "or"
xterm -fg "$color" -fn terminal &
;;
8 *)
xterm -fn terminal
;;
9 esac
10 echo "Out of case command"
EXPLANATION
The user is asked for input. The input is assigned to the variable color. The case command evaluates the expression $color. If the color begins with a B or b, followed by the letter l and any two characters, such as blah, blue, blip, and so on, the case expression matches the first value (the wildcards are shell metacharacters). The value is terminated with a single closed parenthesis. The xterm command sets the foreground color to blue. The statement is executed if the value in line number 3 matches the case expression. The double semicolons are required after the last command in this block of commands. Control branches to line 10 when the semicolons are reached. The script is easier to debug if the semicolons are on their own line. If the case expression matches a G or g, followed by the letters ree and ending in zero or more of any other characters, the xterm command is executed. The double semicolons terminate the block of statements and control branches to line 10. The vertical bar is used as an OR conditional operator. If the case expression matches either red or orange, the xterm command is executed. This is the default value. If none of the above values match the case expression, the commands after the *) are executed. The esac statement terminates the case command. After one of the case values are matched, execution continues here.
Creating Menus with the here document and case Command
The here document and case command are often used together. The here document is used to create a menu of choices that will be displayed to the screen. The user will be asked to select one of the menu items, and the case command will test against the set of choices to execute the appropriate command.
Example 14.31.
(From the .bash_profile File)
echo "Select a terminal type: "
1 cat <<- ENDIT
1) unix
2) xterm
3) sun
2 ENDIT
3 read choice
4 case "$choice" in
5 1) TERM=unix
export TERM
;;
2) TERM=xterm
export TERM
;;
6 3) TERM=sun
export TERM
;;
7 esac
8 echo "TERM is $TERM."
(The Command Line and Output)
$ . .bash_profile
Select a terminal type:
1) unix
2) xterm
3) sun
2 <-- User input
TERM is xterm.
EXPLANATION
If this segment of script is put in the .bash_profile, when you log on, you will be given a chance to select the proper terminal type. The here document is used to display a menu of choices. The user-defined ENDIT terminator marks the end of the here document. The read command stores the user input in the variable TERM. The case command evaluates the variable TERM and compares that value with one of the values preceding the closing parenthesis: 1, 2, or 3. The first value tested is 1. If there is a match, the terminal is set to unix. The TERM variable is exported so that subshells will inherit it. A default value is not required. The TERM variable is normally assigned in /etc/profile at login time. If the choice is 3, the terminal is set to sun. The esac terminates the case command. After the case command has finished, this line is executed.
|