The Exit Status and Decision-Making
Overview
All Qshell commands update a numeric exit status upon completion. The exit status indicates whether the command behaved normally or not. You can use the exit status for making decisions, for controlling looping, and for testing and recovering from errors. In this chapter, you will learn about the exit status: how to test it, how to set it, how to use it to make decisions, and how to control loops with it.
Exit status is never less than zero, and never more than 255. An exit status of zero usually indicates that the command ended normally, while a positive value usually indicates an error. However, this is not always the case. The file comparison utility ( cmp ), for example, returns a zero if two files are identical, one if they are different, or two or greater if an error occurs. But cmp is the exception, not the rule.
You can use the special parameter $? to retrieve the exit status, as shown in Figure 7.1. You won't often need to use the $? special parameter, however. Instead, you'll let the Qshell commands act according to the value of the exit status.
cp quack.txt quack.bak cp: 001-2113 Error found getting information for object/home/JSMITH/quack.txt. No such path or directory. /home/JSMITH $ print $? 1
Figure 7.1: The copy fails because there is no file named quack.txt in the current directory. As a result, the exit status is set to a value of one.
Setting the Exit Status
Qshell scripts and subshells also set the exit status before returning to the invoking process. The exit status of the script is the exit status of the last command that ran.
You can use the exit command to specify an exit status of your choosing in Qshell scripts and subshells. As mentioned, this command can return a status in the range zero to 255, like this:
exit 3
This portion of a Qshell script checks to make sure that the caller passed at least one argument:
# make sure the first parm was passed if [ -z ] then echo "Usage: ${0#$PWD/} pattern" >&2 echo [
# make sure the first parm was passed if [ -z $1 ] then echo "Usage: ${0#$PWD/} pattern" >&2 echo [$0] >&2 exit 1 fi
] >&2 exit 1 fi
If the first positional parameter in this example has no value, the script sends a message to stderr (the standard error device, usually the terminal) and exits with an exit status of one.
Figure 7.2 shows the exit command used with the print command. The first print command shows the ID of the interactive shell. The qsh command starts a subshell (a copy of Qshell running within another copy of Qshell). The second print command shows a different ID, which means that the subshell is in control. The exit command shuts down the subshell and returns to the first shell, with an exit status of five. The last print command shows that the exit status was returned from the subshell.
print $$ 9606 /home/JSMITH $ qsh # start a subshell /home/JSMITH $ print $$ 9627 /home/JSMITH $ exit 5 # exit the subshell /home/JSMITH $ print $? 5
Figure 7.2: The exit command can be called to shut down a subshell.
The True, False, and Null Utilities
The true and false utilities perform no action, but return exit-status values zero and one, respectively. These are useful when you need a command to set the exit status, but do not want the command to perform any action.
The null utility, which is written as a single colon , always returns a true status. The null utility is illustrated in Figure 7.3.
true ; print $? /home/JSMITH $ false ; print $? 1 /home/JSMITH $ : ; print $?
Figure 7.3: The true and null utilities set the exit status to zero. The false utility sets the exit status to one.
The if Command
The if command is a decision-making structure that enables a script to take different actions based on the exit status. (Another decision-making structure, case , is covered in the next chapter.)
The syntax of the if utiity is shown below. Note that the brackets indicate optional elements; they are not part of the syntax and should not be keyed:
if list then list [elif list then list ] [ else list ] fi
A list is a group of commands that are treated as a unit. Qshell executes the first list of commands that follows the if . If the exit status of the last command in the list is zero, Qshell executes the list that follows the then . Otherwise, Qshell executes the list in the elif (else if) or else portions of the structure, if they are specified. The end of the if structure is marked with fi .
Commands must be separated from one another, either with semicolons or end-of-line characters . In the if and elif parameters, the exit status of the last command is taken as the exit status of the list.
Some shell programmers prefer to place then on the same line as if or elif , as shown in this syntax:
if list ; then list [ elif list ; then list] [ else list ] fi
Other programmers prefer to place then and else on lines of their own, like this:
if list then list [ elif list then list ] [ else list ] fi
Here is a simple example of the if utility, in which Qshell retrieves the value of the variable todir and attempts to change to a directory whose name is stored in that variable:
if cd $todir then echo Now working from $PWD fi
If the change directory ( cd ) command succeeds, the exit status is zero and Qshell runs the echo command, which displays a message. If cd fails, the exit status is non-zero , echo is skipped , and execution of the script continues after the fi .
In the following example, if is used with the copy ( cp ) command:
if cp -t .copy then echo Copy of to .copy is complete. else echo Copy of failed. fi
The file named in parameter 1 is copied to a file of the same name with .copy appended. That is, file xyz would be copied to xyz.copy. If cp succeeds, the exit status is zero and the first echo statement runs. If cp fails, the exit status is one, so Qshell runs the second echo statement instead.
This example illustrates the else-if ( elif ) command:
if [ -ge 18 ] then categ=A elif [ -ge 12 ] then categ=B elif [ -ge 8 ] then categ=C else categ=D fi
If parameter 1 is less than 18, it's checked against 12. If it's less than 12, it's checked against eight. The else clause takes effect only if the if and elif tests fail. After this code completes, the variable categ has a value of A, B, C, or D.
The following example shows that if structures can be nested:
if [ $pflag = on ] then if [ $cflag = on ] then cp .copy else cp $default .copy fi fi
If variable pflag does not have a value of on , the inner if is never executed.
In the next example, if is followed by two cp commands, separated by end-of-line characters:
if cp data01.txt data99.txt cp data01.txt then mv data99.txt ~/backup else exit 1 fi
Qshell runs both cp commands. If the second command succeeds, Qshell runs the move ( mv ) command. If not, control exits, setting the exit status to one. Whether or not the first cp command succeeds doesn't matter.
The following is equivalent to the previous example, except that semicolons separate the commands in the first list:
if cp data01.txt data99.txt ; cp data01.txt then mv data99.txt ~/backup else exit 1 fi
The Test Utility
The test utility evaluates a condition and sets the exit status. It can do the following:
- Evaluate the type of a file (regular or directory)
- Evaluate file permissions
- Evaluate session status conditions
- Compare strings
- Compare arithmetic expressions
The test utility has two formats:
test expression [ expression ]
The second form is preferred because it is considered easier to read, and because it makes the coding of compound conditions easier. Whichever form you use, however, be sure to leave white space around everything. Each option, string, and operator must be separated from its neighbor by white space. The brackets in the second form must be set off by blank space as well.
As of V5R2, you can use a new version of the test utility, sometimes called the extended conditional . The extended conditional utility is the preferred test construct. Its syntax is as follows :
[[ expression ]]
The main difference between test and the extended conditional under Qshell is that the extended conditional is built-in (directly interpreted by the Qshell interpreter), whereas test is an external utility, which must run in its own process. For that reason, you should use the extended conditional rather than test if your release supports it and compatibility with previous releases of Qshell is not required.
The conditional utilities are usually used with a control flow command like the 'if' command. Although they look unique, there is nothing tricky with conditional utilities formed using brackets. The '[ ]' and '[[ ]]' are another way to specify a test utility. The reason you have so many to choose from is due to the evolution and compatibility requirements of shell interpreters. The test utility evolved into the '[ ]' utility because that utility was syntactically better. The '[ ]' utility evolved into the extended conditional '[[ ]]' because of the benefits awarded a utility built into the command language.
Evaluating File Conditions
The file condition options shown in Table 7.1 test for the existence of a file, and in almost all cases, for some other attribute besides.
Option |
Description |
Release |
---|---|---|
File1 -ef file2 |
File1 and file2 are different names for the same file. |
V5R2 |
-b file |
File is a block special file. |
V4R3 |
-c file |
File is a character special file. |
V4R3 |
-d file |
File is a directory. |
V4R3 |
-e file |
File exists. |
V4R3 |
-f file |
File is a regular file (not a directory). |
V4R3 |
-g file |
File's set- group -ID flag is set. |
V4R3 |
-G file |
File is owned by the effective group ID. |
V5R2 |
-h file |
File is a symbolic link. |
V4R3 |
-k file |
File's sticky bit is set. |
V4R3 |
-L file |
File is a symbolic link; equivalent to the -h option. |
V4R3 |
-N file |
File is a native object. |
V4R3 |
File1 -nt file2 |
File1 is newer than file2, or file2 does not exist. |
V5R2 |
-O file |
File is owned by the effective user ID. |
V5R2 |
File1 -ot file2 |
File1 is older than file, or file2 does not exist. |
V5R2 |
-p file |
File is a pipe. |
V4R3 |
-r file |
File is readable. |
V4R3 |
-s file |
File size is greater than zero. |
V4R3 |
-S file |
File is a socket. |
V4R3 |
-u file |
File's set-user-ID flag is set. |
V4R3 |
-W file |
File is writable. |
V4R3 |
-x file |
File is executable. |
V4R3 |
The following simple example illustrates the use of the -d file condition:
if [[ -d "" ]] ; then cd ; fi
If the value in parameter 1 is the name of a directory, Qshell changes to that directory. Notice that the first instance of $1 is in double quotes. Quoting the argument keeps the test from failing if parameter 1 has no value.
The isempty.qsh script in Figure 7.4 uses the -s option to determine whether or not the file in parameter 1 has a size greater than zero bytes. If it does, Qshell runs the null command, which does nothing but set the exit status to zero. Otherwise, the script sends a message to stdout (the standard output device, by default the terminal) and sets the exit status to two.
cat isempty.qsh if [[ -s "" ]] then : else echo "File is empty" exit 2 fi /home/JSMITH $ ls -l a -rw-rw-rw- 1 SMITH 0 0 Oct 3 09:50 a /home/JSMITH $ isempty.qsh a File a is empty /home/JSMITH $ print $? 2 /home/JSMITH $ ls -l goodoleboys.txt -rwxrwx--- 1 SMITH 0 746 Sep 6 14:50 goodoleboys.txt /home/JSMITH $ isempty.qsh goodoleboys.txt /home/JSMITH $ print $?
Figure 7.4: Notice that file a is empty, but file goodoleboys.txt is not.
The isempty.qsh script in Figure 7.5 does not return the message "File $1 is empty" if the file does not exist. Instead, the last else list is executed if the file in parameter 1 does not exist.
cat isempty.qsh if [[ -f "" ]] then if [[ -s "" ]] then : else echo "File is empty" exit 2 fi else echo "File is not a regular file." exit 1 fi
Figure 7.5: This script is a slight improvement on the previous one.
Evaluating Status Conditions
The test utility can evaluate two status conditions, as shown in Table 7.2. To enable and disable options, use the set utility to open file descriptors (see the discussion in chapter 10).
Option |
Description |
Release |
---|---|---|
-o opt |
Shell option opt is enabled. |
V5R2 |
-t fd |
File descriptor fd is open and associated with a terminal. |
V4R3 |
The following example tests for status conditions:
if [[ -o noglob ]]
This condition proves true if globbing ( path name expansion) has been turned off. That is, this condition is true if the noglobbing option has been turned on.
A more complex example is shown in Figure 7.6. Script read01.qsh is executed twice in this example. The first time, stdin is not redirected; the prompt string is displayed. The second time, stdin is redirected to file phonedir.csv, so the prompt string is not displayed.
cat read01.qsh #! /bin/qsh if [ -t 0 ] then print "Enter name,age,phone." fi IFS=',' read name age p echo $name echo $age echo $phone /home/JSMITH $ read01.qsh Enter name,age,phone. Joe,35,456-3456 Joe 35 456-3456 /home/JSMITH $ read01.qsh Larry 22 543-9876
Figure 7.6: Script read01.qsh tests file descriptor zero to see if the standard input device (stdin) is assigned to a terminal or not. If stdin is assigned to a terminal, Qshell executes the print command to display a prompt for the user. If stdin is not assigned to a terminal, the prompt is not displayed.
Comparing Strings
Table 7.3 lists the operators Qshell uses for string comparisons. The greater-than and less-than string comparison operators do not have to be quoted when used with the extended conditional. They must be quoted when used with test , however. Since these operators are not available on releases prior to V5R2, you should use the extended conditional with the greater-than and less-than string comparison operators.
Operator |
Description |
Release |
---|---|---|
-n string |
String length is not zero. (Variable has a non-null value.) |
V4R3 |
-z string |
String length is zero. (Variable is null or unset.) |
V4R3 |
string |
String is not the null string. |
V4R3 |
string1 = string2 |
String1 is equal to string2. |
V4R3 |
string1 == string2 |
String1 is equal to string2. This syntax is newer than the single equal sign, and is preferred in Unix circles. |
V5R2 |
string1 != string2 |
String1 is not equal to string2. |
V4R3 |
string1 < string2 |
String1 is before string2, according to the collating sequence. (This applies to the extended conditional only.) |
V5R2 |
string1 > string2 |
String1 is after string2, according to the collating sequence. (This applies to the extended conditional only.) |
V5R2 |
Here are four brief code fragments that illustrate the string comparison operators:
- Variable kflag is initially set to the value off . If the first parameter has the value -k , the script sets kflag to on and shifts out the first parameter:
kflag=off if [ "" = "-k" ] then kflag=on shift fi
- The condition proves true if the value in the day variable has the value Sat:
if [ "$day" = Sat ]
It is a good practice to place double quotes around variable names because Qshell will issue an error message if a variable with no value is used in a comparison test.
- The condition proves true if the day variable is unset or null:
if [ -z "$day" ]
- The test proves true if variable answer has a value of lowercase y . Notice that the "equal" test operator is two adjacent equal signs:
if [[ $answer == 'y' ]]
Comparing Arithmetic Expressions
Arithmetic comparisons are based on numeric values. The arithmetic comparison operators, which are listed in Table 7.4, are different from the string comparison operators. You will get strange errors if you confuse the two.
Option |
Description |
Release |
---|---|---|
exp1 -eq exp2 |
Expression1 is equal to expression2. |
V4R3 |
exp1 -ne exp2 |
Expression1 is not equal to expression2. |
V4R3 |
exp1 -gt exp2 |
Expression1 is greater than expression2. |
V4R3 |
exp1 -ge exp2 |
Expression1 is greater than or equal to expression2. |
V4R3 |
exp1 -lt exp2 |
Expression1 is less than expression2. |
V4R3 |
exp1 -le exp2 |
Expression1 is less than or equal to expression2. |
V4R3 |
Here is an example of how you might use a numeric comparison:
if [ $# -ne 3 ] then echo "Usage: ${0#$PWD/} pattern fromfile tofile" >&2 exit 1 fi
The script from which this code is taken expects three positional parameters ”no more, and no less. If three parameters are not passed to the script, the echo command sends an error message to stderr and exits the script with an exit status of one.
Several more examples of the comparison operators are shown in Figure 7.7. The first two tests use the string comparison operator, =, so 03 does not match 3 . The last two tests use the arithmetic comparison operator, -eq, so 03 does match 3 .
eval nbr=3 /home/JSMITH $ if [ $nbr = 3 ] ; then echo true ; else echo false ; fi true /home/JSMITH $ if [ $nbr = 03 ] ; then echo true ; else echo false ; fi false /home/JSMITH $ if [ $nbr -eq 3 ] ; then echo true ; else echo false ; fi true /home/JSMITH $ if [ $nbr -eq 03 ] ; then echo true ; else echo false ; fi true
Figure 7.7: These lines illustrate the difference between the string comparison operators and the arithmetic comparison operators.
Compound Conditions
Qshell permits you to group simple conditional expressions to form compound conditions. The compound conditional operators are listed in Table 7.5.
Operator |
Description |
Release |
---|---|---|
! expr |
The expression is false. |
V4R3 |
expr1 -a expr2 expr1 & expr2 |
Both expression1 and expression2 are true. |
V4R3 |
expr1 && expr2 |
Both expression1 and expression2 are true. This form is newer than the -a form, and is preferred. |
V5R2 |
expr1 -o expr2 expr1 expr2 |
Either expression1 or expression2 is true, or both are true. |
V4R3 |
expr1 expr2 |
Either expression1 or expression2 is true, or both are true. This form is newer than the -o form, and is preferred. |
V5R2 |
(expr) |
The expression is evaluated regardless of hierarchy of operators. |
V4R3 |
The And operators are evaluated before the Or operators. As usual, you may alter the order of the hierarchy by placing an expression in parentheses. Since the ampersand and pipe characters (& and ) and the open and close parentheses have special meanings to Qshell, precede them with a backslash to "escape" them or use the extended conditional. As part of the command language, the extended conditional lets Qshell know that the ampersand, pipe, and parenthesis operators are being used to form expressions. Qshell will then avoid their other special meanings.
Here is a simple compound condition, which will create a file with the name in the first parameter, if it doesn't already exist:
if [ ! -e "" ] ; then touch ; fi
In the following compound condition, if the value in parameter 1 is not the name of a directory, a backup copy of the file will be made:
if [ ! -d "" ] # if parm 1 is not a directory then cp .bak fi
The backup copy of the file will have the same name as the original, with .bak appended.
All three tests in the following lines are identical. Each test proves true either if kflag is on and day is Sat, or if day is Sun:
if [[ $kflag = on && $day = Sat $day = Sun ]] if [ $kflag = on -a $day = Sat -o $day = Sun ] if [ $kflag = on & $day = Sat $day = Sun ]
The following three tests are also identical:
if [[ $kflag = on && ($day = Sat $day = Sun) ]] if [ $kflag = on -a ($day = Sat -o $day = Sun ) ] if [ $kflag = on & ($day = Sat $day = Sun ) ]
These tests prove true if kflag is on and day is either Sat or Sun. The parentheses, which must be preceded by backslashes, force Qshell to evaluate the Or operation before evaluating the And operation. The & and symbols in the third test must also be preceded by backslashes. However, the extended conditional (in the first test) does not require the parentheses to be "escaped" with backslashes.
Conditional Execution
You can use the && (And) and (Or) operators between separate tests as a type of conditional execution. The result of one command determines whether or not another command runs. In an And list, the second command runs only if the first one succeeds (i.e., returns an exit status of zero.) In an Or list, the second command runs only if the first command fails. The exit status of the compound expression is the exit status of the last command to run.
Note that && and look exactly like the && and that were introduced in the previous section. However, they are not the same. The && and operators in this section are used to connect separate tests. In the previous section, the && and were used to form compound conditions within a single test. The && and of the previous section can only be used with V5R2 and any releases that follow it. The && and of this section are available with previous releases.
The remainder of this section provides several examples to help you better understand conditional execution. In the first example, if the file named in positional parameter 1 exists and is a regular file, delete it:
[ -f ] && rm
This is equivalent to the following code:
if [ -f "" ] then rm "" fi
In the following example, Qshell attempts to make a backup copy of the file named in the first parameter:
cp .copy echo File backup failed! >&2
If the copy succeeds, control passes to the next statement. If the copy fails, Qshell runs the echo command, sending a message to stderr. This is equivalent to the following code:
if cp .copy then : else echo File backup failed! fi
The following test proves true if parameter 1 is between 91 and 100, inclusive:
if [ "" -ge "91" ] && [ "" -le "100" ]
If the value in the first parameter is greater than or equal to 91, Qshell checks to see if the value of the first parameter is less than or equal to 100. If the value of the first parameter is less than 91, the second test is unnecessary, it is not carried out. Skipping evaluation of unnecessary parts of the expression is commonly referred to as a "short circuit" evaluation.
Qshell performs the tests from left to right, so in the following line, if the value of day is Sat , Qshell ignores the second test because of the Or condition:
if [ $day = Sat ] [ $day = Sun ] && [ $kflag = on ]
If the value of day is not Sat , Qshell carries out the second test. If either of the first two tests is successful, Qshell evaluates the third test. If both of the first two tests fail, the third test is never carried out. The entire condition proves true if day is either Sat or Sun , and kflag has the value on .
The following if statement has the same tests as the previous example, but the order has been changed:
if [ $kflag = on ] && [ $day = Sat ] [ $day = Sun ]
Qshell performs the tests from left to right. If kflag has the value on , the second test is carried out. If the first two tests are both successful, the third test is not carried out. The third test is carried out only if either of the first two tests fails. Again, the entire condition proves true if kflag has the value on and day is Sat or Sun .
The example below also has the same tests, but the second and third tests are in parentheses:
if [ $kflag = on ] && ([ $day = Sat ] [ $day = Sun ])
In this case, if the first test proves true, Qshell performs the second and third tests in a subshell. The subshell performs the Or and returns a zero or non-zero exit status. Once again, the entire condition proves true if kflag has the value on and day is either Sat or Sun .
Looping Structures Governed by the Exit Status
The while and until looping structures are both governed by the exit status. The while loop is a top- tested loop. The body of the loop runs again and again as long as the exit status is true (zero). If the exit status is not zero when while is first tested, the body of the loop never runs at all.
The until loop is a bottom-tested loop. The exit status is examined after each iteration, so the body is executed at least once. It loops until the exit status is zero.
The while and until constructs are similar in syntax, as you can see here:
while list do list done until list do list done
Like the if command, it is common for some Qshell programmers to combine parts of these commands on the same line, like this:
while list ; do list done until list ; do list done
The lists referred to in the syntax statements are lists of commands. If a list has more than one command, the exit status of the last command determines the result of the condition. Separate the commands in a list with end-of-line characters (using the Enter key) or semicolons.
It is common in shell script programming to use a while loop to process all the input parameters. Here is such a loop:
# process the parameters while [ ] do echo shift done
The echo command displays each parameter's value. In a real application, echo would be replaced with the appropriate processing logic.
This while loop uses a test utility to determine whether or not the first parameter is defined. If the first parameter has a value, the script displays the value and discards it. Eventually, the last defined parameter will be shifted out, and para meter 1 will become null, stopping the loop. If no parameters are passed to the script, the loop is never entered, and the echo and shift commands never run.
Here is an example of the until loop:
until [ -f wakeup ] do sleep 60 done # more commands here
Qshell continues to loop until a file named wakeup exists in the current directory. The sleep command suspends execution for 60 seconds before each test.
Break and Continue
The break and continue utilities provide additional control over while , until , and for loops . The break utility is used to immediately exit a loop; continue starts the next iteration of a loop. The syntax of these two commands is shown here:
break [n] continue [n]
The parameter ( n ) is optional, to indicate how many levels of loops are affected. It defaults to one.
Figures 7.8 through 7.10 provide additional examples of the looping structures. In Figure 7.8, if a line has the value *EOD , the script ignores the remainder of the input.
ct=0 while read line do if [[ $line == "*EOD" ]] then break fi let ct+=1 print $ct: $line done
Figure 7.8: The break command prematurely ends a loop.
In Figure 7.9, if the first character of an input record is the pound sign indicating a comment, the continue command is executed, skipping the let and print commands.
ct=0 while read line do if [[ ${line:0:1} == "#" ]] then continue fi let ct+=1 print $ct: $line done
Figure 7.9: The continue command begins the next iteration of a loop.
Figure 7.10 contains two types of commands: those that must be done at least once, and those that may not be done at all. The true sets up an infinite loop that must be terminated with break . The prompt string will be displayed and the file name will be read at least once. If the user enters a hyphen the first time through the while , the other commands in the script will not be executed at all. If the user enters a name that is not the name of a regular file, the continue causes Qshell to skip the rest of the commands in the loop.
while true do print "Enter a file name, or - to quit" read filename if [[ "$filename" = '-' ]] then break fi if [[ ! -f "$filename" ]] then print "File $filename is not a regular file." continue fi if cp $filename ${filename}.bak then print "File $filename has been backed up." else print "File $filename backup failed." fi done
Figure 7.10: Middle-tested loops contain elements of both top-tested and bottom- tested loops.
Summary
Testing and looping are fundamental parts of every programming language. Qshell has strong testing and looping structures. The if utility tests a wide variety of conditions, including file and directory tests, string comparisons, and numeric comparisons.
Two looping structures, while and until , were introduced in this chapter. The while structure implements top- tested loops , which govern commands that might not be executed at all. The until structure implements bottom-tested loops, which govern commands that must be executed at least once.
Chapter 8 Additional Control Structures
Категории