Shell Programming


Combining and Negating test Conditions



Yüklə 241,75 Kb.
səhifə2/4
tarix08.10.2017
ölçüsü241,75 Kb.
#3709
1   2   3   4

Combining and Negating test Conditions

The expressions that have been discussed thus far are called primary expressions because each tests only one condition. The characters following the hyphen are the operators, and the terms to the right and left of the operators are the arguments. Some of the operators, like the numeric comparison operators, are binary because they always have two arguments, one on the right and one on the left. Some of the operators, like the file test options, are unary because the operator takes only one argument, which is always placed on the right.

Sometimes you may not be interested in what is true, but in what is not true. To find out what is not true, you can use the unary negation operator, the exclamation (!), in front of any primary. Create an empty file and try some of the file operators shown in the following example:

$ cat >empty


Ctrl+d
$ test -r empty
$ echo $?
0
$ test -s empty
$ echo $?
1
$ test ! -s empty
$ echo $?
0
$

The primary expressions in a test command can be combined with a logical and operator, -a, or with a logical or operator, -o. When you use the -a operator, the combined expression is true if and only if both of the primary expressions are true. When you use the -o operator, the combined expression is true if either of the primary expressions is true. Using the empty file from above, test to see whether the file is readable and contains data:

$ test -r empty -a -s empty
$ echo $?
1
$

The combined expression is false. The first expression is true because the file is readable, but the second expression fails because the file has a size of 0.



A Shorthand Method of Doing Tests

Because the test command is such an important part of shell programming, and to make shell programs look more like programs in other languages, the Bourne shell has an alternative method for using test: you enclose the entire expression in square brackets ([]).

$ int1=4
$ [ $int1 -gt 2 ]
$ echo $?
0
$

Remember that even though it looks different, the preceding example is still the test command and the same rules apply.

Using test, you can make the unload program from listing 11.2 more user friendly, as well as more bullet proof, by making sure that a valid directory name is entered on the command line. The revised program is shown in listing 11.3.

Listing 11.3. Program using test for error checking.

# unload - program to backup and remove files


# syntax - unload directory
# check arguments
if [ $# -ne 1 ]
then
echo "usage: unload directory"
exit 1
fi
# check for valid directory name
if [! -d "$1" ]
then
echo "$1 is not a directory"
exit 2
fi
cd $1
ls -a | cpio -o >/dev/rmt0
if [ $? -eq 0 ]
then
rm *
else
echo "A problem has occurred in creating the backup."
echo "The directory will not be erased."
echo "Please check the backup device and try again."
exit 3
fi

There are several items of interest in the revised program in listing 11.3. One is the introduction of the exit statement. The exit statement has two purposes: to stop any further commands in the program from being executed and to set the exit status of the program. By setting a nonzero exit status, subsequent programs can check the $? variable to see whether unload is successful. Notice that in the test to see whether the argument is a valid directory, the variable substitution is made within double quotation marks. Using double quotation marks prevents the test command from failing if the program were called with an argument containing only blanks; the test still fails, but the user does not see the error message from test. One other change to the program is to remove the actual backup command from the if statement and place it on a line by itself and then use test on the exit status to make the decision. Although using if to check the exit status of the backup is legitimate and probably more efficient, the meaning may be unclear to the casual observer.

Consider the traveltime program shown in listing 11.1. Suppose you execute the program with the following command line:

$ traveltime 61 60


The trip will take 1 hours and 1 minutes

Although this answer is correct, it may make your English teacher cringe. You can use numeric testing and if-then-else statements to make the output more palatable. The revised program is shown in listing 11.4.



Listing 11.4. Revised traveltime program.

# traveltime - a program to calculate how long it will


# take to travel a fixed distance
# syntax: traveltime miles mph
X60='expr $1 \* 60'
TOTMINUTES='expr $X60 / $2'
HOURS='expr $TOTMINUTES / 60'
MINUTES='expr $TOTMINUTES % 60'
if [ $HOURS -gt 1 ]
then
DISPHRS=hours
else
DISPHRS=hour
fi
if [ $MINUTES -gt 1 ]
then
DISPMIN=minutes
else
DISPMIN=minute
fi
echo "The trip will take $HOURS $DISPHRS \c"
if [ $MINUTES -gt 0 ]
then
echo "and $MINUTES $DISPMIN"
else
echo
fi

Now traveltime supplies the appropriate singular or plural noun depending on the amount of time:

$ traveltime 50 40
The trip will take 1 hour and 15 minutes
$ traveltime 121 60
The trip will take 2 hours and 1 minute
$ traveltime 120 60
The trip will take 2 hours
$

The Null Command

You have now enhanced the unload program to accept the name of a directory from the command line, to check for a valid directory name, and to give the user of the program more information on any errors that may occur. The only real difference between the unload function and the backup function is that unload removes the files from the directory after it has been archived. It would seem that a simple modification to unload—taking out the rm statement—would transform unload to an enhanced version of backup. The only problem is that the rm command is the only command following a then statement, and there must be at least one command following every then statement. The Bourne shell provides a solution with the null command. The null command, represented by a colon (:), is a place holder whose purpose is to fulfill a requirement where a command must appear. To change unload to backup, you replace the rm command with the null command and change some of the messages.

# backup - program to backup all files in a directory
# syntax - backup directory
# check arguments
if [ $# -ne 1 ]
then
echo "usage: backup directory"
exit 1
fi
# check for valid directory name
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 2
fi
cd $1
ls -a | cpio -o >/dev/rmt0
if [ $? -eq 0 ]
then
:
else
echo "A problem has occurred in creating the backup."
echo "Please check the backup device and try again."

Displaying the Program Name

In the previous two examples, a helpful message was displayed for the user who failed to enter any command-line arguments.

In this message, the name of the program is displayed as part of a literal string. However, if you renamed this program, this message would no longer be valid. In the Bourne shell, the variable $0 always contains the name of the program, as entered on the command line. You can now make the program more general, as in the following example:

if [ $# -ne 1 ]


then
echo "usage: $0 directory"
exit 1
fi

Nested if Statements and the elif Construct

Often you may want your program to do the following:



1.

Check for a primary condition, and

a.

If the primary condition is true, perform an operation.

b.

If the primary condition is false, check a secondary condition.

(1)

If the secondary condition is true, perform another operation, but

(2)

If the secondary condition is false, check a third condition.




If the third condition is true, perform another operation.

You can do so by nesting if-else statements, as in the following syntax:

if command


then
command
else
if command
then
command
else
if command
then
command
fi
fi
fi

Nesting can be useful but can also be confusing, especially knowing where to place the fi statements. Because this kind of programming occurs frequently, the Bourne shell provides a special construct called elif, which stands for else-if and indicates a continuation of the main if statement. You could restate the sequence described above with elif statements, as follows:

if command
then
command
elif command
then
command
elif command
then
command
fi

Either method produces the same results. You should use the one that makes the most sense to you.



Reading Data into a Program Interactively

Up to this point, all the input to your programs has been supplied by users in the form of command-line arguments. You can also obtain input for a program by using the read statement. The general syntax of the read statement is as follows:

read var1 var2 ... varn

When the Bourne shell encounters a read statement, the standard input file is read until the shell reads a newline character. When the shell interprets the line of input, it does not make filename and variable substitutions, but it does remove excess white space. After it removes white space, the shell puts the value of the first word into the first variable, and the second word into the second variable, and so on until either the list of variables or the input line is exhausted. If there are more words in the input line than in the variable list, the last variable in the list is assigned the remaining words in the input line. If there are more variables in the list than words in the line, the leftover variables are null. A word is a group of alphanumeric characters surrounded by whitespace.

In the following example, the read statement is looking for three variables. Since the line of input contains three words, each word is assigned to a variable.

$ read var1 var2 var3


Hello my friend
$ echo $var1 $var2 $var3
Hello my friend
$ echo $var1
Hello
$ echo $var2
my
$ echo $var3
friend
$

In the next example, the read statement is looking for three variables, but the input line consists of four words. In this case, the last two words are assigned to the third variable.

$ read var1 var2 var3
Hello my dear friend
$ echo $var1
Hello
$ echo $var2
my
$ echo $var3
dear friend
$

Finally, in this example, the input line contains fewer words than the number of variables in the read statement, so the last variable remains null.

$ read var1 var2 var3
Hello friend
$ echo $var1
Hello
$ echo $var2
friend
$ echo $var3
$

Suppose that you want to give the user of the unload program in Listing 11.3 the option to abort. You might insert these lines of code:

...
echo "The following files will be unloaded"
ls -x $1
echo "Do you want to continue: Y or N \c"
read ANSWER
if [ $ANSWER = N -o $ANSWER = n ]
then
exit 0
fi
...

In the preceding example, you use the \c character in the user prompt so that the user's response appears on the same line as the prompt. The read statement will cause the program to pause until the operator responds with a line of input. The operator's response will be stored in the variable ANSWER. When you're testing the user's response, you use the -o operator so that the appropriate action is taken, regardless of whether the user's response is in upper- or lowercase.



The case Statement

Earlier in this section, you saw that the Bourne shell provided a special construct for a common occurrence by providing the elif statement to be used in place of nested if-then-else constructs. Another fairly common occurrence is a series of elif statements where the same variable is tested for many possible conditions, as in the following:

if [ variable1 = value1 ]
then
command
command
elif [ variable1 = value2 ]
then
command
command
elif [ variable1 = value3 ]
then
command
command
fi

The Bourne shell provides a cleaner and more powerful method of handling this situation with the case statement. The case statement is cleaner because it does away with the elifs and the thens. It is more powerful because it allows pattern matching, much as the command-line interpreter does. The case statement allows a value to be named, which is almost always a variable, and a series of patterns to be used to match against the value, and a series of commands to executed if the value matches the pattern. The general syntax of case is as follows:

case value in
pattern1)
command
command;;
pattern2)
command
command;;
...
patternn)
command;
esac

The case statement executes only one set of commands. If the value matches more than one of the patterns, only the first set of commands specified is executed. The double semicolons (;;) after a command act as the delimiter of the commands to be executed for a particular pattern match.

In the program in listing 11.5, the case statement combines the three sample programs—backup, restore, and unload—into a single interactive program, enabling the user to select the function from a menu.

Listing 11.5. An interactive archive program.

# Interactive program to restore, backup, or unload


# a directory
echo "Welcome to the menu driven Archive program"
echo _
# Read and validate the name of the directory
echo "What directory do you want? \c"
read WORKDIR
if [ ! -d $WORKDIR ]
then
echo "Sorry, $WORKDIR is not a directory"
exit 1
fi
# Make the directory the current working directory
cd $WORKDIR
# Display a Menu
echo "Make a Choice from the Menu below"
echo _
echo "1 Restore Archive to $WORKDIR"
echo "2 Backup $WORKDIR "
echo "3 Unload $WORKDIR"
echo
# Read and execute the user's selection
echo "Enter Choice: \c"
read CHOICE
case "$CHOICE" in
1) echo "Restoring..."
cpio -i 2) echo "Archiving..."
ls | cpio -o >/dev/rmt0;;
3) echo "Unloading..."
ls | cpio -o >/dev/rmt0;;
*) echo "Sorry, $CHOICE is not a valid choice"
exit 1
esac
#Check for cpio errors
if [ $? -ne 0 ]
then
echo "A problem has occurred during the process"
if [ $CHOICE = 3 ]
then
echo "The directory will not be erased"
fi
echo "Please check the device and try again"
exit 2
else
if [ $CHOICE = 3 ]
then
rm *
fi
fi

In the program in listing 11.5, notice the use of the asterisk (*) to define a default action if all the other patterns in the case statement fail to match. Also notice that the check for errors in the archive process occurs only once in the program. This check can be done in this program because the exit status of the case statement is always the exit status of the last command executed. Because all three cases end with the execution of cpio, and the default case ends with an exit statement, the exit status variable at this point in this program is always the exit status of cpio.

Another powerful capability of the case statement is to allow multiple patterns to be related to the same set of commands. You use a vertical bar (|) as an or symbol in the following form:

pattern1 | pattern2 ) command


command;;

You can further modify the interactive archive program to allow the user to make a choice by entering either the menu number or the first letter of the function, by changing the case statement:

read CHOICE
case "$CHOICE" in
1 | R ) echo "Restoring..."
cpio -i 2 | B ) echo "Archiving..."
ls | cpio -o >/dev/rmt0;;
3 | U ) echo "Unloading..."
ls | cpio -o >/dev/rmt0;;
*) echo "Sorry, $CHOICE is not a valid choice"
exit 1
esac

Building Repetitions into a Program

Up to now, the programs you have looked at have had a top-to-bottom, linear progression. The program statements are executed from top to bottom. One of the most beneficial things about computer programs is their capability to process data in volume. For this to occur, the programming language must have some construct to cause portions of the program to be repetitive. In computer terminology, this construct is often called looping.

For example, suppose you had a computer file containing records with mailing addresses and ZIP codes and you wanted to print only records matching a specific ZIP code. You would want to write a program which reads a record, performs a matching test on the ZIP code, prints those that match, and then repeat the process until the data is exhausted. You could do this within a loop.

The Bourne shell has three different looping constructs built into the language. One of the key concepts in program looping is the termination of the loop. Many hours of computer time are wasted by programs that inadvertently go into infinite loops. The main difference between the shell's three looping constructs is the method by which the loop is terminated. The three types of loops are the while loop, the until loop, and the for loop; each is discussed separately in the following sections.



Repeating Within a while Loop

The while construct enables you to specify commands that will be executed while some condition is true.

The general format of the while construct is as follows:

while command


do
command
command
...
command
done

Consider the following example in a program called squares in listing 11.6.



Listing 11.6. Example of a while loop.

# squares - prints the square of integers in succession


int=1
while [ $int -lt 5 ]
do
sq='expr $int \* $int'
echo $sq
int='expr $int + 1'
done
echo "Job Complete"
$ squares
1
4
9
16
Job Complete
$

In the program in listing 11.6, as long as the value of int is less than five, the commands inside the loop are executed. On the fifth repetition, the test condition associated with the while statement returns a nonzero value, and the command following the done statement is executed.

In the interactive archive program in Listing 11.5, the user is allowed to make a single request and the program terminates. Using while, you can change the program to allow the user to enter multiple requests. The revised program is shown in listing 11.7.

Listing 11.7. Revised interactive archive program.

# Interactive program to restore, backup, or unload


# a directory
echo "Welcome to the menu driven Archive program"
ANSWER=Y
while [ $ANSWER = Y -o $ANSWER = y ]
do
echo _
# Read and validate the name of the directory
echo "What directory do you want? \c"
read WORKDIR
if [ ! -d $WORKDIR ]
then
echo "Sorry, $WORKDIR is not a directory"
exit 1
fi
# Make the directory the current working directory
cd $WORKDIR
# Display a Menu
echo "Make a Choice from the Menu below"
echo _
echo "1 Restore Archive to $WORKDIR"
echo "2 Backup $WORKDIR "
echo "3 Unload $WORKDIR"
echo
# Read and execute the user's selection
echo "Enter Choice: \c"
read CHOICE
case "$CHOICE" in
1) echo "Restoring..."
cpio -i 2) echo "Archiving..."
ls | cpio -o >/dev/rmt0;;
3) echo "Unloading..."
ls | cpio -o >/dev/rmt0;;
*) echo "Sorry, $CHOICE is not a valid choice"
esac
#Check for cpio errors
if [ $? -ne 0 ]
then
echo "A problem has occurred during the process"
if [ $CHOICE = 3 ]
then
echo "The directory will not be erased"
fi
echo "Please check the device and try again"
exit 2
else
if [ $CHOICE = 3 ]
then
rm *
fi
fi
echo "Do you want to make another choice? \c"
read ANSWER
done

By initializing the ANSWER variable to Y, enclosing the main part of the program within a while loop, and getting a new ANSWER at then end of the loop in the program in listing 11.7, the user is able to stay in this program until he or she answers N to the question.



Repeating Within an until Loop

The while construct causes the program to loop as long as some condition is true. The until construct is the complement to while; it causes the program to loop until a condition is true. These two constructs are so similar, you can usually use either one. Use the one that makes the most sense in the context of the program you are writing.

The general format of the until construct is as follows:

until command


do
command
command
...
command
done

You could have made the modification to the interactive archive program just as easily with an until loop by replacing the while with until:

until [ $ANSWER = N -o $ANSWER = n ]

Processing an Arbitrary Number of Parameters with shift

Before considering the for loop, it would be helpful to look at the shift command, since the for loop is really a shorthand use of shift.

In the examples presented so far, the number of positional parameters, or command-line arguments, is either presumed to be solitary or is passed on to a command as a whole using the $* variable. If a program needs to process each of the command-line arguments individually, and the number of arguments is not known, you can process the arguments one by one by using the shift command in your program. The shift command shifts the position of positional parameters by one; $2 becomes $1, $3 becomes $2, and so on. The parameter that was $1 before the shift command is not available after shift. The following simple program illustrates this concept:

# shifter


until [ $# -eq 0 ]
do
echo "Argument is $1 and `expr $# - 1` argument(s) remain"
shift
done
$ shifter 1 2 3 4
Argument is 1 and 3 argument(s) remain
Argument is 2 and 2 argument(s) remain
Argument is 3 and 1 argument(s) remain
Argument is 4 and 0 argument(s) remain
$

You may have noticed that the $# variable decremented each time the shift command was executed in the preceding example. Using this knowledge, you can use an until loop to process all the variables. Consider the example in listing 11.8, a program to sum an integer list supplied as command-line arguments.



Yüklə 241,75 Kb.

Dostları ilə paylaş:
1   2   3   4




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©www.genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə