Shell Programming



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

ICS 335

Shell Programming Notes

In this major section, you learn how to put commands together in such a way that the sum is greater than the parts. You learn some UNIX commands that are useful mainly in the context of shell programs. You also learn how to make your program perform functions conditionally based on logical tests that you define, and you learn how to have parts of a program repeat until its function is completed. In short, you learn how to use the common tools supplied with UNIX to create more powerful tools specific to the tasks you need to perform.



What Is a Program?

A wide assortment of definitions exist for what is a computer program, but for this discussion, a computer program is an ordered set of instructions causing a computer to perform some useful function. In other words, when you cause a computer to perform some tasks in a specific order so that the result is greater than the individual tasks, you have programmed the computer. When you enter a formula into a spreadsheet, for example, you are programming. When you write a macro in a word processor, you are programming. When you enter a complex command like

$ ls -R / | grep myname | pg

in a UNIX shell, you are programming the shell; you are causing the computer to execute a series of utilities in a specific order, which gives a result that is more useful than the result of any of the utilities taken by itself.



A Simple Program

Suppose that daily you back up your data files with the following command:

$ cd /usr/home/myname; ls * | cpio -o >/dev/rmt0

As you learned earlier, when you enter a complex command like this, you are programming the shell. One of the useful things about programs, though, is that they can be placed in a program library and used over and over, without having to do the programming each time. Shell programs are no exception. Rather than enter the lengthy backup command each time, you can store the program in a file named backup:

$ cat >backup
cd /usr/home/myname
ls * | cpio -o >/dev/rmt0
Ctrl+d

You could, of course, use your favorite editor (see Chapter 7, "Editing Text Files"), and in fact with larger shell programs, you almost certainly will want to. You can enter the command in a single line, as you did when typing it into the command line, but because the commands in a shell program (sometimes called a shell script) are executed in sequence, putting each command on a line by itself makes the program easier to read. Creating easy-to-read programs becomes more important as the size of the programs increase.

Now to back up your data files, you need to call up another copy of the shell program (known as a subshell) and give it the commands found in the file backup. To do so, use the following command:

$ sh backup

The program sh is the same Bourne shell that was started when you logged in, but when a filename is passed as an argument, instead of becoming an interactive shell, it takes its commands from the file.

An alternative method for executing the commands in the file backup is to make the file itself an executable. To do so, use the following command:

$ chmod +x backup

Now you can back up your data files by entering the newly created command:

$ backup

If you want to execute the commands in this manner, the file backup must reside in one of the directories specified in the environment variable $PATH.



The Shell as a Language

If all you could do in a shell program was to string together a series of UNIX commands into a single command, you would have an important tool, but shell programming is much more. Like traditional programming languages, the shell offers features that enable you to make your shell programs more useful, such as: data variables, argument passing, decision making, flow control, data input and output, subroutines, and handling interrupts.

By using these features, you can automate many repetitive functions, which is, of course, the purpose of any computer language.

Using Data Variables in Shell Programs

You usually use variables within programs as place holders for data that will be available when the program is run and that may change from execution to execution. Consider the backup program:

cd /usr/home/myname
ls | cpio -o >/dev/rmt0

In this case, the directory to be backed up is contained in the program as a literal, or constant, value. This program is useful only to back up that one directory. The use of a variable makes the program more generic:

cd $WORKDIR
ls * | cpio -o >/dev/rmt0

With this simple change, any user can use the program to back up the directory that has been named in the variable $WORKDIR, provided that the variable has been exported to subshells. See "Making Variables Available to Subshells with export" earlier in this chapter.



Entering Comments in Shell Programs

Quite often when you're writing programs, program code that seemed logical six months ago may be fairly obscure today. Good programmers annotate their programs with comments. You enter comments into shell programs by inserting the pound sign (#) special character. When the shell interpreter sees the pound sign, it considers all text to the end of the line as a comment.



Doing Arithmetic on Shell Variables

In most higher level programming languages, variables are typed, meaning that they are restricted to certain kinds of data, such as numbers or characters. Shell variables are always stored as characters. To do arithmetic on shell variables, you must use the expr command.

The expr command evaluates its arguments as mathematical expressions. The general form of the command is as follows:

expr integer operator integer

Because the shell stores its variables as characters, it is your responsibility as a shell programmer to make sure that the integer arguments to expr are in fact integers. Following are the valid arithmetic operators:

+ Adds the two integers.

- Subtracts the second integer from the first.

* Multiplies the two integers.

/ Divides the first integer by the second.

% Gives the modulus (remainder) of the division.


$ expr 2 + 1
3
$ expr 5 - 3
2

If the argument to expr is a variable, the value of the variable is substituted before the expression is evaluated, as in the following example:

$ $int=3
$ expr $int + 4
7

You should avoid using the asterisk operator (*) alone for multiplication. If you enter

$ expr 4 * 5

you get an error because the shell sees the asterisk and performs filename substitution before sending the arguments on to expr. The proper form of the multiplication expression is

$ expr 4 \* 5
20

You also can combine arithmetic expressions, as in the following:

$ expr 5 + 7 / 3
7

The results of the preceding expression may seem odd. The first thing to remember is that division and multiplication are of a higher precedence than addition and subtraction, so the first operation performed is 7 divided by 3. Because expr deals only in integers, the result of the division is 2, which is then added to 5, giving the final result 7. Parentheses are not recognized by expr, so to override the precedence, you must do that manually. You can use back quotation marks to change the precedence, as follows:

$ int='expr 5 + 7'
$ expr $int / 3
4

Or you can use the more direct route:

$ expr 'expr 5 + 7' / 3
4

Passing Arguments to Shell Programs

A program can get data in two ways: either it is passed to the program when it is executed as arguments, or the program gets data interactively. An editor such as vi is usually used in an interactive mode, whereas commands such as ls and expr get their data as arguments. Shell programs are no exception. In the section "Reading Data into a Program Interactively," you see how a shell program can get its data interactively.

Passing arguments to a shell program on a command line can greatly enhance the program's versatility. Consider the inverse of the backup program presented earlier:

$ cat >restoreall


cd $WORKDIR
cpio -i Ctrl+d

As written, the program restoreall reloads the entire tape made by backup. But what if you want to restore only a single file from the tape? You can do so by passing the name of the file as an argument. The enhanced restore1 program is now:

# restore1 - program to restore a single file
cd $WORKDIR
cpio -i $1

Now you can pass a parameter representing the name of the file to be restored to the restore1 program:

$ restore1 file1

Here, the filename file1 is passed to restore1 as the first positional parameter. The limitation to restore1 is that if you want to restore two files, you must run restore1 twice.

As a final enhancement, you can use the $* variable to pass any number of arguments to the program:

# restoreany - program to restore any number of files


cd $WORKDIR
cpio -i $* $ restoreany file1 file2 file3

Because shell variables that have not been assigned a value always return null, or empty, if the restore1 or restoreany programs are run with no command-line parameters, a null value is placed in the cpio command, which causes the entire archive to be restored.

Consider the program in listing 11.1; it calculates the length of time to travel a certain distance.

Listing 11.1. Program example with two parameters.

# 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'
echo "The trip will take $HOURS hours and $MINUTES minutes"

The program in listing 11.1 takes two positional parameters: the distance in miles and the rate of travel in miles per hour. The mileage is passed to the program as $1 and the rate of travel as $2. Note that the first command in the program multiplies the mileage by 60. Because the expr command works only with integers, it is useful to calculate the travel time in minutes. The user-defined variable X60 holds an interim calculation that, when divided by the mileage rate, gives the total travel time in minutes. Then, using both integer division and modulus division, the number of hours and number of minutes of travel time is found.

Now execute the traveltime for a 90-mile trip at 40 mph with the following command line:

$ traveltime 90 40


The trip will take 2 hours and 15 minutes

Decision Making in Shell Programs

One of the things that gives computer programming languages much of their strength is their capability to make decisions. Of course, computers don't think, so the decisions that computer programs make are only in response to conditions that you have anticipated in your program. The decision making done by computer programs is in the form of conditional execution: if a condition exists, then execute a certain set of commands. In most computer languages, this setup is called an if-then construct.



The if-then Statement

The Bourne shell also has an if-then construct. The syntax of the construct is as follows:

if command_1
then
command_2
command_3
fi
command_4

You may recall that every program or command concludes by returning an exit status. The exit status is available in the shell variable $?. The if statement checks the exit status of its command. If that command is successful, then all the commands between the then statement and the fi statement are executed. In this program sequence, command_1 is always executed, command_2 and command_3 are executed only if command_1 is successful, and command_4 is always executed.

Consider a variation of the backup program, except that after copying all the files to the backup media, you want to remove them from your disk. Call the program unload and allow the user to specify the directory to be unloaded on the command line, as in the following example:

# unload - program to backup and remove files


# syntax - unload directory
cd $1
ls -a | cpio -o >/dev/rmt0
rm *

At first glance, it appears that this program will do exactly what you want. But what if something goes wrong during the cpio command? In this case, the backup media is a tape device. What if the operator forgets to insert a blank tape in the tape drive? The rm command would go ahead and execute, wiping out the directory before it has been backed up! The if-then construct prevents this catastrophe from happening. A revised unload program is shown in listing 11.2.



Listing 11.2. Shell program with error checking.

# unload - program to backup and remove files


# syntax - unload directory
cd $1
if ls -a | cpio -o >/dev/rmt0
then
rm *
fi

In the program in listing 11.2, the rm command is executed only if the cpio command is successful. Note that the if statement looks at the exit status of the last command in a pipeline.



Data Output from Shell Programs

The standard output and error output of any commands within a shell program are passed on the standard output of the user who invokes the program unless that output is redirected within the program. In the example in listing 11.2, any error messages from cpio would have been seen by the user of the program. Sometimes you may write programs that need to communicate with the user of the program. In Bourne shell programs, you usually do so by using the echo command. As the name indicates, echo simply sends its arguments to the standard output and appends a newline character at the end, as in the following example:

$ echo "Mary had a little lamb"
Mary had a little lamb

The echo command recognizes several special escape characters that assist in formatting output. They are as follows:

\b Backspace

\c Prints line without newline character

\f Form Feed: advances page on a hard copy printer; advances to new screen on a display terminal

\n Newline

\r Carriage return

\t Tab


\v Vertical Tab

\\ Backslash

\0nnn A one-, two-, or three-digit octal integer representing one of the ASCII characters

If you want to display a prompt to the user to enter the data, and you want the user response to appear on the same line as the prompt, you use the \c character, as follows:

$ echo "Enter response:\c"
Enter response$

The if-then-else Statement

A common desire in programming is to perform one set of commands if a condition is true and a different set of commands if the condition is false. In the Bourne shell, you can achieve this effect by using the if-then-else construct:

if command_1
then
command_2
command_3
else
command_4
command_5
fi

In this construct, command_1 is always executed. If command_1 succeeds, the command_2 and command_3 are executed; if it fails, command_4 and command_5 are executed.

You can now enhance the unload program to be more user friendly. For example,

# unload - program to backup and remove files


# syntax - unload directory
cd $1
if ls -a | cpio -o >/dev/rmt0
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."
fi




T
IP:
Because the shell ignores extra whitespace in a command line, good programmers use this fact to enhance the readability of their programs. When commands are executed within a then or else clause, indent all the commands in the clause the same distance.




Testing Conditions with test

You've seen how the if statement tests the exit status of its command to control the order in which commands are executed, but what if you want to test other conditions? A command that is used a great deal in shell programs is the test command. The test command examines some condition and returns a zero exit status if the condition is true and a nonzero exit status if the condition is false. This capability gives the if statement in the Bourne shell the same power as other languages with some enhancements that are helpful in shell programming.

The general form of the command is as follows:

test condition

The conditions that can be tested fall into four categories: 1) String operators that test the condition or relationship of character strings; 2) Integer relationships that test the numerical relationship of two integers; 3) File operators that test for the existence or state of a file; 4) Logical operators that allow for and/or combinations of the other conditions.

Testing Character Data

You learned earlier that the Bourne shell does not type cast data elements. Each word of an input line and each variable can be taken as a string of characters. Some commands, such as expr and test, have the capability to perform numeric operations on strings that can be translated to integer values, but any data element can be operated on as a character string.

You can compare two strings to see whether they are equivalent or not equivalent. You also can test a single string to see whether it has a value or not. The string operators are as follows:


str1 = str2

True if str1 is the same length and contains the same characters as str2

str1 != str2

True if str1 is not the same as str2

-n str1

True if the length of str1 is greater than 0 (is not null)

-z str1

True if str1 is null (has a length of 0)

str1

True if str1 is not null

Even though you most often use test with a shell program as a decision maker, test is a program that can stand on its own as in the following:

$ str1=abcd


$ test $str1 = abcd
$ echo $?
0
$

Notice that unlike the variable assignment statement in the first line in the preceding example, the test command must have the equal sign surrounded by white space. In this example, the shell sends three arguments to test. Strings must be equivalent in both length and characters by character.

$ str1="abcd "
$ test "$str1" = abcd
$ echo $?
1
$

In the preceding example, str1 contains five characters, the last of which is a space. The second string in the test command contains only four characters. The nonequivalency operator returns a true value everywhere that the equivalency operator returns false.

$ str1=abcd
$ test $str1 != abcd
$ echo $?
1
$

Two of the string operations, testing of a string with no operator and testing with the -n operator, seem almost identical, as the following example shows.

$ str1=abcd
$ test $str1
$ echo $?
0
$ test -n $str1
$ echo $?
0
$

The difference between the two commands in the preceding example is a subtle one, but it points out a potential problem in using the test command, as shown in the following example of two different tests:

$ str1=" "
$ test $str1
$ echo $?
1
$ test "$str1"
$ echo $?
0
$ test -n $str1
test: argument expected
$ test -n "$str1
$ echo $?
0

In the preceding example, the first test is false. Why? Remember that the shell interpreter makes variable substitutions before it processes the command line, and when it processes the command line, it removes excess whitespace. Where $str1 does not have double quotation marks, the blanks are passed to the command line and stripped; when the double quotation marks are used, the blanks are passed on to test. What happens in the third test? When the interpreter removes the whitespace, test is passed only the -n option, which requires an argument.



Testing Numeric Data

The test command, like expr, has the capability to convert strings to integers and perform numeric operations. Whereas expr performs arithmetic on integers, test performs logical comparisons. The available numerical comparisons are as follows:



int1 -eq int2

True if int1 is numerically equal to int2

int1 -ne int2

True if int1 is not equal to int2

int1 -gt int2

True if int1 is greater than int2

int1 -ge int2

True if int1 is greater than or equal to int2

int1 -lt int2

True if int1 is less than int2

int1 -le int2

True if int1 is less than or equal to int2

This difference between numeric equivalency and string equivalency is shown in the following example, which defines two strings and then compares them using numeric equivalency first and then string equivalency.

$ str1=1234


$ str2=01234
$ test $str1 = $str2
$ echo $?
1
$ test $str1 -eq $str2
$ echo $?
0
$

In the second case here, the strings were converted to integers and found to be numerically equivalent, whereas the original strings were not.



Testing for Files

The third type of condition that test can examine is the state of files. Using the test command in your program, you can determine whether a file exists, whether it can be written to, and several other conditions. All the file test options return true, only if the file exists. The file test options are



-r filenm

True if the user has read permission

-w filenm

True if the user has write permission

-x filenm

True if the user has execute permission

-f filenm

True if filenm is a regular file

-d filenm

True if filenm is a directory

-c filenm

True if filenm is a character special file

-b filenm

True if filenm is a block special file

-s filenm

True if the size of filenm is not zero

-t fnumb

True if the device associated with the file descriptor fnumb (1 by default) is a terminal device

Yüklə 241,75 Kb.

Dostları ilə paylaş:
  1   2   3   4




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

    Ana səhifə