Shell Programming


Listing 11.8. An integer summing program



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

Listing 11.8. An integer summing program.

# sumints - a program to sum a series of integers


#
if [ $# -eq 0 ]
then
echo "Usage: sumints integer list"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum='expr $sum + $1'
shift
done
echo $sum

Following is the execution of sumints:

$ sumints 12 18 6 21
57
$

You also can use the shift command for another purpose. The Bourne shell predefines nine positional parameters, $1 through $9. This does not mean that only nine positional parameters can be entered on the command line, but to access positional parameters beyond the first nine, you must use the shift command.

The shift command can take an integer argument that causes it to shift more than one position at a time. If you know that you have processed the first three positional parameters, for example, and you want to begin a loop to process the remaining arguments, you can make $4 shift to $1 with the following command:

shift 3.


Repeating Within a for Loop

The third type of looping construct in the Bourne shell is the for loop. The for loop differs from the other constructs in that it is not based on a condition being true or false. Instead the for loop executes one time for each word in the argument list it has been supplied. For each iteration of the loop, a variable name supplied on the for command line assumes the value of the next word in the argument list. The general syntax of the for loop is as follows:

for variable in arg1 arg2 ... argn
do
command
...
command
done

The following simple example illustrates the construct:

$ for LETTER in a b c d; do echo $LETTER; done
a
b
c
d
$

Because the argument list contained four words, the loop is executed exactly four times. The argument list in the for command does not have to be a literal constant; it can be from a variable substitution.

You can also write the sumints program in listing 11.8 using a for loop, by passing the command-line arguments to the for loop. The modified program appears in listing 11.9.

Listing 11.9. Modified integer summing program.

# sumints - a program to sum a series of integers


#
if [ $# -eq 0 ]
then
echo "Usage: sumints integer list"
exit 1
fi
sum=0
for INT in $*
do
sum='expr $sum + $INT'
done
echo $sum

Getting Out of a Loop from the Middle

Normally, a looping construct executes all the commands between the do statement and the done statement. Two commands enable you to get around this limitation: the break command causes the program to exit the loop immediately, and the continue command causes the program to skip the remaining commands in the loop but remain in the loop.

A technique that is sometimes used in shell programming is to start an infinite loop, that is, a loop that will not end until either a break or continue command is executed. An infinite loop is usually started with either a true or false command. The true command always returns an exit status of zero, whereas the false command always returns a nonzero exit status. The loop

while true


do
command
...
command
done

executes until either your program does a break or the user initiates an interrupt. You can also write an infinite loop as follows:

until false
do
command
...
command
done

We could use this technique to make the interactive archive program of Listing 11.7 a little easier to use. The revised program is shown in listing 11.10.



Listing 11.10. Another version of the interactive archiver.

# Interactive program to restore, backup, or unload


# a directory
echo "Welcome to the menu driven Archive program"
while true
do
# Display a Menu
echo
echo "Make a Choice from the Menu below"
echo _
echo "1 Restore Archive"
echo "2 Backup directory"
echo "3 Unload directory"
echo "4 Quit"
echo
# Read the user's selection
echo "Enter Choice: \c"
read CHOICE
case $CHOICE in
[1-3] ) 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"
continue
fi
# Make the directory the current working directory
cd $WORKDIR;;
4) :;;
*) echo "Sorry, $CHOICE is not a valid choice"
continue _
esac
case "$CHOICE" in
1) echo "Restoring..."
cpio -i 2) echo "Archiving..."
ls | cpio -o >/dev/rmt0;;
3) echo "Unloading..."
ls | cpio -o >/dev/rmt0;;
4) echo "Quitting"
break;;
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"
continue
else
if [ $CHOICE = 3 ]
then
rm *
fi
fi
done

In the program in listing 11.1, the loop continues as long as true returns a zero exit status, which is always, or until the user makes selection four, which executes the break command and terminates the loop. Notice also, that if the user makes an error in choosing the selection or in entering the directory name, the continue statement is executed rather than the exit statement. This way, the user can stay in the program even if he or she makes a mistake in entering data, but the mistaken data cannot be acted on.

Notice also the use of two case statements. The first case statement requests that the operator enter a directory name only if option 1, 2, or 3 is selected. This example illustrates how pattern matching in a case statement is similar to that on a command line. In the first case statement, if the user selects option 4, the null command (:) is executed. Because the first case statement checks for invalid selections and executes a continue if an invalid selection is made, the second case statement need not check for any but valid selections.

Structured Shell Programming Using Functions

A common feature among higher level programming languages is the ability to group computer instructions together into functions that can be called from anywhere within the program. These functions are sometimes called subroutines. The Bourne shell also provides you this ability.

The general syntax of a function definition is as follows:

funcname ()


{
command
... _
command;
}

Once it is defined, a function can be called from anywhere within the shell by using funcname as a command. There are two reasons you might want to group commands into a function. One good reason is to break a complex program into more manageable segments, creating a structured program. A structured program might take the following form:

# start program
setup ()
{ command list ; }_
do_data ()
{ command list ; }_
cleanup ()
{ command list ; }_
errors ()
{ command list ; }_
setup
do_data
cleanup
# end program

In the above example, setup, do_data, and cleanup are functions. When you look at a well-structured program, the names of the functions give you a fair idea of what the functions might do. If you were trying to analyze this, you might assume what the setup and cleanup functions do and concentrate on the do_data section.






T
IP:
Always give variables and functions meaningful names. It may seem at the time you are writing a program that you will remember what the variables and functions are used for, but experience has proven that after the passage of time things are not always so clear. You should also remember that there will probably come a time when someone else will look at your programs, and that person will appreciate descriptive names.


Another legitimate reason for grouping commands into functions is that you may want to execute the same sequence of commands from several points within a program. At several points in the interactive archive program in listing 11.10, a non-fatal error occurs and the continue command is executed. You can give the user the option of continuing at each of these points with an interactive continue function named icontinue.

icontinue ()
{
while true
do
echo "Continue? (y/n) \c"
read ANSWER
case $ANSWER in
[Yy] ) return 0;;
[Nn] ) return 1;;
* ) echo "Answer y or n";;
esac
done
}

Now you can replace the continue statements in the program with the icontinue function.

if icontinue then continue else break fi

All of the prompting, reading, and error checking are carried out by the icontinue function, instead of repeating these commands at every continue point. This example also illustrates the function's capability to return an exit status with return. If no return command is available within the function, the exit status of the function is the exit status of the last command in the function.

Shell functions are very much like shell programs—with one very important difference. Shell programs are executed by subshells, whereas shell functions are executed as part of the current shell. Therefore, functions can change variables that are seen in the current shell. Functions can be defined in any shell, including the interactive shell.

$ dir () { ls -l; }_


$ dir
-rw-rw-r— 1 marsha adept 1024 Jan 20 14:14 LINES.dat
-rw-rw-r— 1 marsha adept 3072 Jan 20 14:14 LINES.idx
-rw-rw-r— 1 marsha adept 256 Jan 20 14:14 PAGES.dat
-rw-rw-r— 1 marsha adept 3072 Jan 20 14:14 PAGES.idx
-rw-rw-r— 1 marsha acct 240 May 5 1992 acct.pds
$

You have now defined dir as a function within your interactive shell. It remains defined until you log off or unset the function, as follows:

$ unset dir

Functions can also receive positional parameters, as in the following example:

$ dir () {_
> echo "Permission Ln Owner Group File Sz Last Access"
> echo "————— — —— —— ——— —————"
> ls -l $*;
>}
$ dir L*
Permission Ln Owner Group File Sz Last Access
————— — —— —— ——— —————_
-rw-rw-r— 1 marsha adept 1024 Jan 20 14:14 LINES.dat
-rw-rw-r— 1 marsha adept 3072 Jan 20 14:14 LINES.idx

In this example, the argument L* was passed to the dir function and replaced in the ls command for $*.

Normally, a shell script is executed in a subshell. Any changes made to variables in the subshell are not made in the parent shell. The dot (.) command causes the shell to read and execute a shell script within the current shell. You make any function definitions or variable assignments in the current shell. A common use of the dot command is to reinitialize login values by rereading the .profile file. For information about .profile, see "Customizing the Shell" later in this chapter.

$ . .profile



Handling the Unexpected with trap

When you're writing programs, one thing to keep in mind is that programs do not run in a vacuum. Many things can happen during a program that are not under the control of the program. The user of the program may press the interrupt key or send a kill command to the process, or the controlling terminal may become disconnected from the system. In UNIX, any of these events can cause a signal to be sent to the process. The default action when a process receives a signal is to terminate.

Sometimes, however, you may want to take some special action when a signal is received. If a program is creating temporary data files, and it is terminated by a signal, the temporary data files remain. In the Bourne shell, you can change the default action of your program when a signal is received by using the trap command.

The general format of the trap command is as follows:

trap command_string signals

On most systems, you can trap 15 signals. The default action for most is to terminate the program, but this action can vary, so check your system documentation to see what signals can occur on your system (Part IV, "Process Control" discusses signals in more detail). Any signal except 9 (known as the sure kill signal) can be trapped, but usually you are concerned only with the signals that can occur because of the user's actions. Following are the three most common signals you'll want to trap:



Signal


Description


1

Hangup

2

Operator Interrupt

15

Software Termination (kill signal)

If the command string contains more than one command, which it most certainly should, you must enclose the string in either single or double quotation marks. The type of quotation marks you use determines when variable substitution is made.

Suppose you have a program that creates some temporary files. When the program ends normally, the temporary files are removed, but receiving a signal causes the program to terminate immediately, which may leave the temporary files on the disk. By using the trap command in the following example, you can cause the temporary files to be removed even if the program does not terminate normally due to receiving a hangup, interrupt, or kill signal:

trap "rm $TEMPDIR/*$$; exit" 1 2 15

When the trap command is executed, the command string is stored as an entry in a table. From that point on, unless the trap is reset or changed, if the signal is detected, the command string is interpreted and executed. If the signal occurs in the program before the trap command is executed, the default action occurs. It is important to remember that the shell reads the command string twice—once when the trap is set and again when the signal is detected. This determines the distinction between the single and double quotation marks. In the preceding example, when the trap command line is read by the interpreter, variable substitution takes place for $TEMPDIR and $$. After the substitution, the resultant command string is stored in the trap table. If the trap command is changed to use single quotation marks

trap 'rm $TEMPDIR/*$$; exit' 1 2 15

when trap is executed, no variable substitution take place, and the command string

rm $TEMPDIR/*$$; exit

is placed in the trap table. When the signal is detected, the command string in the table is interpreted, and then the variable substitution takes place. In the first instance, $TEMPDIR and $$ have the value that they had at the time the trap was executed. In the second instance, $TEMPDIR and $$ assume the value that they have at the time the signal is detected. Make sure that you know which you want.

The command string for the trap command almost always contains an exit statement. If you don't include an exit statement, then the rm command is executed when the signal is detected, and the program picks right up where it left off when the signal occurred. Sometimes you might want the program to pick up where it left off instead of exiting. For example, if you don't want your program to stop when the terminal is disconnected, you can trap the hangup signal, specifying the null command, as shown in the following example:

trap : 1


You can set a trap back to the default by executing the trap command with no command string, like this:

trap 1


The following command has the effect of making the user press the interrupt key twice to terminate a program:

trap 'trap 2' 2



Conditional Command Execution with the And/Or Constructs

As you have already seen, often you can write a shell program more than one way without changing the results of the program. The until statement, for example, is simply a reverse way of using a while statement. You can cause commands to be conditionally executed using the if-then-else construct, but you also can accomplish conditional execution using the && and || operators. In the C programming language, these symbols represent the logical and and the logical or operations respectively. In the Bourne shell, the && connects two commands in such a way that the second command is executed only if the first command is successful.

The general format of && is as follows:

command && command

For example, in the statement

rm $TEMPDIR/* && echo "Files successfully removed"

the echo command is executed only if the rm command is successful. You also can do this programming in an if-then statement like this one:

if rm $TEMPDIR/*


then
echo "Files successfully removed"
fi

Conversely, the || connects to commands in such a way that the second command is executed only if the first command is not successful, as in this command:

rm $TEMPDIR/* || echo "Files were not removed"

The preceding is the programming equivalent of

if rm $TEMPDIR/*
then
:
else
echo "Files were not removed"
fi

You also can concatenate these operators. In the following command line, command3 is executed only if both command1 and command2 are successful:

command1 && command2 && command3

You can also concatenate operators of different types. In the following command line, command3 is executed only if command1 is successful and command2 is unsuccessful:

command1 && command2 || command3

The && and || are simple forms of conditional command execution and are usually used only in cases where single commands are to be executed. Although the commands can be compound, if too many commands appear in this format, the program can be difficult to read. Generally, if-then constructs seem to be more clear if you use more than one or two commands.



Reading UNIX-Style Options

One of the nicer things about UNIX is that most of the standard commands have a similar command-line format:

command -options parameters

If you are writing shell programs for use by other people, it is nice if you use the same conventions. To help you do so, a special command is available in the Bourne shell for reading and processing options in this format: the getopts command, which has the following form:

getopts option_string variable

where option_string contains the valid single-character options. If getopts sees the hyphen (-) in the command input stream, it compares the character following the hyphen with the characters in option_string. If a match occurs, getopts sets variable to the option; if the character following the hyphen does not match one of the characters in option_string, variable is set to a question mark (?). If getopts sees no more characters following a hyphen, it returns a nonzero exit status. This capability enables you to use getopts in a loop.

The program in listing 11.11 illustrates how you use getups to handle options for the date command. The program creates a version of date, which conforms to standard UNIX style, and it adds some options.

Listing 11.11. A standardized date function newdate.

#newdate
if [ $# -lt 1 ]


then
date
else
while getopts mdyDHMSTjJwahr OPTION
do
case $OPTION
in
m) date '+%m ';; # Month of Year
d) date '+%d ';; # Day of Month
y) date '+%y ';; # Year
D) date '+%D ';; # MM/DD/YY
H) date '+%H ';; # Hour
M) date '+%M ';; # Minute
S) date '+%S ';; # Second
T) date '+%T ';; # HH:MM:SS
j) date '+%j ';; # day of year
J) date '+%y%j ';;# 5 digit Julian date
w) date '+%w ';; # Day of the Week
a) date '+%a ';; # Day abbreviation
h) date '+%h ';; # Month abbreviation
r) date '+%r ';; # AM-PM time
\?) echo "Invalid option $OPTION";;
esac
done
fi

In the program in listing 11.11, each option is processed in turn. When getopts has processed all the options, it returns a nonzero exit status, and the while loop terminates. Notice that getopts allows options to be stacked behind a single hyphen, which is also a common UNIX form.

The following examples illustrate how newdate works:

$ newdate -J


94031
$ newdate -a -h -d
Mon
Jan
31
$ newdate -ahd
Mon
Jan
31
$

Sometimes an option requires an argument, which getopts also parses if you follow the option letter in option_string with a colon. When getopts sees the colon, it looks for a value following a space following the option flag. If the value is present, getopts stores the value in a special variable OPTARG. If it can find no value where one is expected, getopts stores a question mark in OPTARG and writes a message to standard error.

The program in listing 11.12 makes copies of a file and gives the copies a new name. The -c option takes an argument specifying the number of copies to make, and the -v option instructs the program to be verbose, that is to display the names of the new files as they are created.

Listing 11.12. duplicate program.

# Syntax: duplicate [-c integer] [-v] filename


# where integer is the number of duplicate copies
# and -v is the verbose option
COPIES=1
VERBOSE=N
while getopts vc: OPTION
do
case $OPTION
in
c) COPIES=$OPTARG;;
v) VERBOSE=Y;;
\?) echo "Illegal Option"
exit 1;;
esac
done
if [ $OPTIND -gt $# ]
then
echo "No file name specified"
exit 2
fi
shift 'expr $OPTIND -1'
FILE=$1
COPY=0
while [ $COPIES -gt $COPY ]
do
COPY='expr $COPY + 1'
cp $FILE ${FILE}${COPY}
if [ VERBOSE = Y ]
then
echo ${FILE}${COPY}
fi
done

In the program in listing 11.12, allowing the user to enter options presents a unique problem; when you write the program, you don't know which of the positional parameters will contain the name of the file that is to be copied. The getopts command helps out by storing the number of the next positional parameter in the variable OPTIND. In the duplicate program, after getopts has located all the options, OPTIND is checked to make sure that a filename is specified and then the shift command makes the filename the first positional parameter.

$ duplicate -v fileA
fileA1
$ duplicate -c 3 -v fileB
fileB1
fileB2
fileB3

Customizing the Shell

The shell performs some very specific tasks and expects its input to follow some specific guidelines—command names first, for instance. But the Bourne shell does allow the user some control over his or her own environment. You can change the look of your shell and even add your own commands.



Customizing the Shell with Environment Variables

In the section "Variables" earlier in this chapter, you learned that one type of variable is called an environment variable. The shell refers to these variables when processing information. Changing the value of an environment variable changes how the shell operates. You can change your command-line prompt, get mail forwarded to you, and even change the way the shell looks at your input.



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ə