Chapter 7. Passing Arguments

Shell programs become far more useful after you learn how to process arguments passed to them. In this chapter, you'll learn how to write shell programs that take arguments typed on the command line. Recall the program run that you wrote in Chapter 5, “And Away We Go,” to run the file sys.caps through tbl, nroff, and lp:

$ cat run
tbl sys.caps | nroff -mm –Tlp | lp
$

Suppose that you need to run other files besides sys.caps through this same command sequence. You could make a separate version of run for each such file; or, you could modify the run program so that you could specify the name of the file to be run on the command line. That is, you could change run so that you could type

run new.hire

for example, to specify that the file new.hire is to be run through this command sequence, or

run sys.caps

to specify the file sys.caps.

Whenever you execute a shell program, the shell automatically stores the first argument in the special shell variable 1, the second argument in the variable 2, and so on. These special variables—more formally known as positional parameters—are assigned after the shell has done its normal command-line processing (that is, I/O redirection, variable substitution, filename substitution, and so on).

To modify the run program to accept the name of the file as an argument, all you do to the program is change the reference to the file sys.caps so that it instead references the first argument typed on the command line:

$ cat run
tbl $1 | nroff -mm -Tlp | lp
$ run new.hire                 Execute it with new.hire as the argument
request id is laser1-24 (standard input)
$

Each time you execute the run program, whatever word follows on the command line will be stored inside the first positional parameter by the shell. In the example, new.hire will be stored in this parameter. Substitution of positional parameters is identical to substitution of other types of variables, so when the shell sees

tbl $1

it replaces the $1 with the first argument supplied to the program: new.hire.

As another example, the following program, called ison, lets you know if a specified user is logged on:

$ cat ison
who | grep $1
$ who                          See who's on
root     console Jul 7 08:37
barney   tty03   Jul 8 12:28
fred     tty04   Jul 8 13:40
joanne   tty07   Jul 8 09:35
tony     tty19   Jul 8 08:30
lulu     tty23   Jul 8 09:55
$ ison tony
tony     tty19   Jul 8 08:30
$ ison pat
$                              Not logged on

The $# Variable

Whenever you execute a shell program, the special shell variable $# gets set to the number of arguments that were typed on the command line. As you'll see in the next chapter, this variable can be tested by the program to determine whether the correct number of arguments was typed by the user.

The next program called args was written just to get you more familiar with the way arguments are passed to shell programs. Study the output from each example and make sure that you understand it:

$ cat args                              Look at the program
echo $# arguments passed
echo arg 1 = :$1: arg 2 = :$2: arg 3 = :$3:
$ args a b c                            Execute it
3 arguments passed
arg 1 = :a: arg 2 = :b: arg 3 = :c:
$ args a b                              Try it with two arguments
2 arguments passed
arg 1 = :a: arg 2 = :b: arg 3 = ::      Unassigned args are null
$ args                                  Try it with no arguments
0 arguments passed
arg 1 =:: arg 2 =:: arg 3 = ::
$ args "a b c"                          Try quotes
1 arguments passed
arg 1 = :a b c: arg 2 = :: arg 3 = ::
$ ls x*                                 See what files start with x
xact
xtra
$ args x*                               Try file name substitution
2 arguments passed
arg 1 = :xact: arg 2 = :xtra: arg 3 = ::
$ my_bin=/users/steve/bin
$ args $my_bin                          And variable substitution
1 arguments passed
arg 1 = :/users/steve/bin: arg 2 = :: arg 3 = ::
$ args $(cat names)                     Pass the contents of names
7 arguments passed
arg 1 = :Charlie: arg 2 = :Emanuel: arg3 = :Fred:
$

As you can see, the shell does its normal command-line processing even when it's executing your shell programs. This means that you can take advantage of the normal niceties such as filename substitution and variable substitution when specifying arguments to your programs.

The $* Variable

The special variable $* references all the arguments passed to the program. This is often useful in programs that take an indeterminate or variable number of arguments. You'll see some more practical examples later. Here's a program that illustrates its use:

$ cat args2
echo $# arguments passed
echo they are :$*:
$ args2 a b c
3 arguments passed
they are :a b c:
$ args2 one                two
2 arguments passed
they are :one two:
$ args2
0 arguments passed
they are ::
$ args2 *
8 arguments passed
they are :args args2 names nu phonebook stat xact xtra:
$

A Program to Look Up Someone in the Phone Book

Here's the phonebook file from previous examples:

$ cat phonebook
Alice Chebba    973-555-2015
Barbara Swingle 201-555-9257
Liz Stachiw     212-555-2298
Susan Goldberg  201-555-7776
Susan Topple    212-555-4932
Tony Iannino    973-555-1295
$

You know how to look up someone in the file by using grep:

$ grep Cheb phonebook
Alice Chebba    973-555-2015
$

And you know that if you want to look up someone by the full name, you'd better put quotes around it to keep the argument together:

$ grep "Susan T" phonebook
Susan Topple    212-555-4932
$

It would be nice to write a shell program that you could use to look up someone. Let's call the program lu and have it take as its argument the name of the person to look up:

$ cat lu
#
# Look someone up in the phone book
#

grep $1 phonebook
$

Here's a sample use of lu:

$ lu Alice
Alice Chebba    973-555-2015
$ lu Susan
Susan Goldberg  201-555-7776
Susan Topple    212-555-4932
$ lu "Susan T"
grep: can't open T
phonebook:Susan Goldberg  201-555-7776
phonebook:Susan Topple    212-555-4932
$

In the preceding example, you were careful to enclose Susan T in double quotes; so what happened? Look again at the grep executed in the lu program:

grep $1 phonebook

Even though enclosing Susan T inside double quotes results in its getting passed to lu as a single argument, when the shell substitutes this value for $1 on grep's command line, it then passes it as two arguments to grep. (Remember we had this same sort of discussion when we talked about variable substitution—first the shell substitutes the value of the variable; then it divides the line into arguments.)

You can alleviate this problem by enclosing $1 inside double quotes (why not single?) in the lu program:

$ cat lu
#
# Look someone up in the phone book -- version 2
#

grep "$1" phonebook
$

Now let's try it again:

$ lu Tony
Tony Iannino   973-555-1295        This still works
$ lu "Susan T"                     Now try this again
Susan Topple   212-555-4932
$

A Program to Add Someone to the Phone Book

Let's continue with the development of programs that work with the phonebook file. You'll probably want to add someone to the file, particularly because our phonebook file is so small. You can write a program called add that takes two arguments: the name of the person to be added and the number. Then you can simply write the name and number, separated from each other by a tab character, onto the end of the phonebook file:

$ cat add
#
# Add someone to the phone book
#

echo "$1        $2" >> phonebook
$

Although you can't tell, there's a tab character that separates the $1 from the $2 in the preceding echo command. This tab must be quoted to make it to echo without getting gobbled up by the shell.

Let's try out the program:

$ add 'Stromboli Pizza' 973-555-9478
$ lu Pizza                         See if we can find the new entry
Stromboli Pizza 973-555-9478       So far, so good
$ cat phonebook                    See what happened
Alice Chebba    973-555-2015
Barbara Swingle 201-555-9257
Liz Stachiw     212-555-2298
Susan Goldberg  201-555-7776
Susan Topple    212-555-4932
Tony Iannino    973-555-1295
Stromboli Pizza 973-555-9478
$

Stromboli Pizza was quoted so that the shell passed it along to add as a single argument (what would have happened if it wasn't quoted?). After add finished executing, lu was run to see whether it could find the new entry, and it did. The cat command was executed to see what the modified phonebook file looked like. The new entry was added to the end, as intended. Unfortunately, the new file is no longer sorted. This won't affect the operation of the lu program, but you can add a sort to the add program to keep the file sorted after new entries are added:

$ cat add
#
# Add someone to the phonebook file -- version 2
#

echo "$1        $2" >> phonebook
sort -o phonebook phonebook
$

Recall that the -o option to sort specifies where the sorted output is to be written, and that this can be the same as the input file:

$ add 'Billy Bach' 201-555-7618
$ cat phonebook
Alice Chebba    973-555-2015
Barbara Swingle 201-555-9257
Billy Bach      201-555-7618
Liz Stachiw     212-555-2298
Stromboli Pizza 973-555-9478
Susan Goldberg  201-555-7776
Susan Topple    212-555-4932
Tony Iannino    973-555-1295
$

So each time a new entry is added, the phonebook file will get re-sorted.

A Program to Remove Someone from the Phone Book

No set of programs that enable you to look up or add someone to the phone book would be complete without a program to remove someone from the phone book. We'll call the program rem and have it take as its argument the name of the person to be removed. What should the strategy be for developing the program? Essentially, you want to remove the line from the file that contains the specified name. The -v option to grep can be used here because it prints lines from a file that don't match a pattern:

$ cat rem
#
# Remove someone from the phone book
#

grep -v "$1" phonebook > /tmp/phonebook
mv /tmp/phonebook phonebook
$

The grep writes all lines that don't match into the file /tmp/phonebook. [1] After the grep is done, the old phonebook file is replaced by the new one from /tmp.

$ rem 'Stromboli Pizza'          Remove this entry
$ cat phonebook
Alice Chebba    973-555-2015
Barbara Swingle 201-555-9257
Billy Bach      201-555-7618
Liz Stachiw     212-555-2298
Susan Goldberg  201-555-7776
Susan Topple    212-555-4932
Tony Iannino    973-555-1295
$ rem Susan
$ cat phonebook
Alice Chebba    973-555-2015
Barbara Swingle 201-555-9257
Billy Bach      201-555-7618
Liz Stachiw     212-555-2298
Tony Iannino    973-555-1295
$

The first case, where Stromboli Pizza was removed, worked fine. In the second case, however, both Susan entries were removed because they both matched the pattern. You can use the add program to add them back to the phone book:

$ add 'Susan Goldberg' 201-555-7776
$ add 'Susan Topple' 212-555-4932
$

In Chapter 8, “Decisions, Decisions,” you'll learn how to determine whether more than one matching entry is found and take some other action if that's the case. For example, you might want to alert the user that more than one match has been found and further qualification of the name is required. (This can be very helpful, because most implementations of grep will match everything if an empty string is passed as the pattern.)

Incidentally, before leaving this program, note that sed could have also been used to delete the matching entry. In such a case, the grep could be replaced with

sed "/$1/d" phonebook > /tmp/phonebook

to achieve the same result. The double quotes are needed around the sed command to ensure that the value of $1 is substituted, while at the same time ensuring that the shell doesn't see a command line like

sed /Stromboli Pizza/d phonebook > /tmp/phonebook

and pass three arguments to sed rather than two.

${n}

If you supply more than nine arguments to a program, you cannot access the tenth and greater arguments with $10, $11, and so on. If you try to access the tenth argument by writing

$10

the shell actually substitutes the value of $1 followed by a 0. Instead, the format

${n}

must be used. So to directly access argument 10, you must write

${10}

in your program.

The shift Command

The shift command allows you to effectively left shift your positional parameters. If you execute the command

shift

whatever was previously stored inside $2 will be assigned to $1, whatever was previously stored in $3 will be assigned to $2, and so on. The old value of $1 will be irretrievably lost.

When this command is executed, $# (the number of arguments variable) is also automatically decremented by one:

$ cat tshift                Program to test the shift
echo $# $*
shift
echo $# $*
shift
echo $# $*
shift
echo $# $*
shift
echo $# $*
shift
echo $# $*
$ tshift a b c d e
5 a b c d e
4 b c d e
3 c d e
2 d e
1 e
0
$

If you try to shift when there are no variables to shift (that is, when $# already equals zero), you'll get an error message from the shell (the error will vary from one shell to the next):

prog: shift: bad number

where prog is the name of the program that executed the offending shift.

You can shift more than one “place” at once by writing a count immediately after shift, as in

shift 3

This command has the same effect as performing three separate shifts:

shift
shift
shift

The shift command is useful when processing a variable number of arguments. You'll see it put to use when you learn about loops in Chapter 9, “'Round and 'Round She Goes.”

Exercises

1:

Modify lu so that it ignores case when doing the lookup.

2:

What happens if you forget to supply an argument to the lu program? What happens if the argument is null (as in, lu "")?

3:

The program ison from this chapter has a shortcoming as shown in the following example:

$ ison ed
fred     tty03    Sep  4 14:53
$

The output indicates that fred is logged on, while we were checking to see whether ed was logged on.

Modify ison to correct this problem.

4:

Write a program called twice that takes a single integer argument and doubles its value:

$ twice 15
30
$ twice 0
0
$

What happens if a noninteger value is typed? What if the argument is omitted?

5:

Write a program called home that takes the name of a user as its single argument and prints that user's home directory. So

home steve

would print

/users/steve

if /users/steve is steve's home directory. (Hint: Recall that the home directory is the sixth field stored in the file /etc/passwd.)

6:

Write a program called suffix that renames a file by adding the characters given as the second argument to the end of the name of the file given as the first argument. So

suffix memo1 .sv

should rename memo1 to memo1.sv.

7:

Write a program called unsuffix that removes the characters given as the second argument from the end of the name of the file given as the first argument. So

unsuffix memo1.sv .sv

should rename memo1.sv to memo1. Be sure that the characters are removed from the end, so

unsuffix test1test test

should result in test1test being renamed to test1. (Hint: Use sed and command substitution.)



[1] /tmp is a directory on all Unix systems that anyone can write to. It's used by programs to create “temporary” files. Each time the system gets rebooted, all the files in /tmp are usually removed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.80.15