This chapter presents a final version of the rolo
program. This version is enhanced with additional options and also allows for more general types of entries (other than just names and numbers). The sections in this chapter discuss the individual programs in rolo
, starting with rolo
itself. At the end of this chapter, sample output is shown.
A more practical type of rolodex program would permit more than just the names and numbers to be stored in the phone book. You'd probably want to keep addresses (maybe even electronic mail addresses) there as well. The new rolo
program allows entries in the phone book to consist of multiple lines. For example, a typical entry might be
Steve's Ice Cream 444 6th Avenue New York City 10003 212-555-3021
To increase the flexibility of the program, we're allowing an individual entry to contain as many lines as desired. So another entry in the phone book might read
YMCA (201) 555-2344
To logically separate one entry from the next inside the phone book file, each entry is “packed” into a single line. This is done by replacing the terminating newline characters in an entry with a special character. We arbitrarily chose the caret ^
. The only restriction here is that this character not be used as part of the entry itself.
Using this technique, the first entry shown would be stored in the phone book file as
Steve's Ice Cream^444 6th Avenue^New York City 10003^212-555-3021^
and the second entry shown as
YMCA^(201) 555-2344^
You'll shortly see how convenient it becomes to process the entries when they're stored in this format. Now we'll describe each program written for the rolodex program.
# # rolo - rolodex program to look up, add, # remove and change entries from the phone book # # # Set PHONEBOOK to point to the phone book file # and export it so other progs know about it # if it's set on entry, then leave it alone # : ${PHONEBOOK:=$HOME/phonebook} export PHONEBOOK if [ ! -e "$PHONEBOOK" ] then echo "$PHONEBOOK does not exist!" echo "Should I create it for you (y/n)? c" read answer if [ "$answer" != y ] then exit 1 fi > $PHONEBOOK || exit 1 # exit if the creation fails fi # # If arguments are supplied, then do a lookup # if [ "$#" -ne 0 ] then lu "$@" exit fi # # Set trap on interrupt (DELETE key) to continue the loop # trap "continue" 2 # # Loop until user selects 'exit' # while true do # # Display menu # echo ' Would you like to: 1. Look someone up 2. Add someone to the phone book 3. Remove someone from the phone book 4. Change an entry in the phone book 5. List all names and numbers in the phone book 6. Exit this program Please select one of the above (1-6): c' # # Read and process selection # read choice echo case "$choice" in 1) echo "Enter name to look up: c" read name if [ -z "$name" ] then echo "Lookup ignored" else lu "$name" fi;; 2) add;; 3) echo "Enter name to remove: c" read name if [ -z "$name" ] then echo "Removal ignored" else rem "$name" fi;; 4) echo "Enter name to change: c" read name if [ -z "$name" ] then echo "Change ignored" else change "$name" fi;; 5) listall;; 6) exit 0;; *) echo "Bad choicea";; esac done
Instead of requiring that the user have a phone book file in his or her home directory, the program checks on startup to see whether the variable PHONEBOOK
has been set. If it has, it's assumed that it contains the name of the phone book file. If it hasn't, it's set to $HOME/phonebook
as the default. In either case, the program then checks to see whether the file exists, and if it doesn't, instead of immediately exiting, asks the user whether he would like to have an initial file created. This was added so that first-time users of rolo
can have an empty phone book file created for them by the program.
This version of rolo
also has a couple of new items added to the menu. Because individual entries can be rather long, an editing option has been added to allow you to edit a particular entry. Formerly, the only way to change an entry was to first remove it and then add a new one, a strategy that was perfectly acceptable when the entries were small.
Another option allows for listing of the entire phone book. With this option, just the first and last lines of each entry are displayed. This assumes that the user follows some convention such as putting the name on the first line and the number on the last.
The entire menu selection process was placed inside a while
loop so that rolo
will continue to display menus until the “exit” option is picked from the menu.
A trap
command is executed before the loop is entered. This trap
specifies that a continue
command is to be executed if signal number 2 is received. So if the user presses the Delete key in the middle of an operation (such as listing the entire phone book), the program won't exit but will abort the current operation and simply continue with the loop. This will result in the redisplay of the menu.
Because entries can now span as many lines as desired, the action performed when add
is selected has been changed. Instead of asking for the name and number, rolo
executes the add
program to get the entry from the user.
For the lookup, change, and remove options, a check is made to ensure that the user doesn't simply press the Enter key when asked to type in the name. This avoids the RE error
that grep
issues if it's given a null first argument.
Now let's look at the individual programs that rolo
executes. Each of the original programs has been changed to accommodate the new entry format and also to be more user friendly.
# # Program to add someone to the phonebook file # echo "Type in your new entry" echo "When you're done, type just a single Enter on the line." first= entry= while true do echo ">> c" read line if [ -n "$line" ] then entry="$entry$line^" if [ -z "$first" ] then first=$line fi else break fi done echo "$entry" >> $PHONEBOOK sort -o $PHONEBOOK $PHONEBOOK echo echo "$first has been added to the phone book"
This program adds an entry to the phone book. It continually prompts the user to enter lines until a line with just an Enter is typed (that is, a null line). Each line that is entered is concatenated to the variable entry
, with the special ^
character used to logically separate one line from the next.
When the while
loop is exited, the new entry is added to the end of the phone book, and the file is sorted.
# # Look someone up in the phone book # name=$1 grep "$name" $PHONEBOOK > /tmp/matches$$ if [ ! -s /tmp/matches$$ ] then echo "I can't find $name in the phone book" else # # Display each of the matching entries # while read line do display "$line" done < /tmp/matches$$ fi rm /tmp/matches$$
This is the program to look up an entry in the phone book. The matching entries are written to the file /tmp/matches$$
. If the size of this file is zero, no match was found. Otherwise, the program enters a loop to read each line from the file (remember an entry is stored as a single line in the file) and then display it at the terminal. A program called display
is used for this purpose. This program is also used by the rem
and change
programs to display entries at the terminal.
# # Display entry from the phonebook # echo echo "--------------------------------------" entry=$1 IFS="^" set $entry for line in "$1" "$2" "$3" "$4" "$5" "$6" do printf "| %-34.34s | " $line done echo "| o o |" echo "--------------------------------------" echo
As noted, this program displays an entry passed as its argument. To make the output more aesthetically pleasing, the program actually “draws” a rolodex card. So typical output from display
would look like this:
-------------------------------------- | Steve's Ice Cream | | 444 6th Avenue | | New York City 10003 | | 212-555-3021 | | | | | | o o | --------------------------------------
After skipping a line and then displaying the top of the card, display
changes IFS
to ^
and then executes the set
command to assign each “line” to a different positional parameter. For example, if entry
is equal to
Steve's Ice Cream^444 6th Avenue^New York City 10003^212-555-3021^
executing the set
command assigns Steve's Ice Cream
to $1
, 444 6th Avenue
to $2
, New York City 10003
to $3
, and 212-555-3021
to $4
.
After executing the set
, the program enters a for
loop that will be executed exactly six times, no matter how many lines are contained in the entry (this ensures uniformity of our rolodex cards—the program can be easily modified to “draw” larger-sized cards if needed). If the set
command was executed on Steve's Ice Cream
as shown previously, $5
and $6
would be null, thus resulting in two blank lines to “fill out” the bottom of the card.
The printf
command displays a line exactly 38 characters wide: the leading |
followed by a space followed by the first 34 characters of $line
followed by a space and a |
.
# # Remove someone from the phone book # name=$1 # # Get matching entries and save in temp file # grep "$name" $PHONEBOOK > /tmp/matches$$ if [ ! -s /tmp/matches$$ ] then echo "I can't find $name in the phone book" exit 1 fi # # Display matching entries one at a time and confirm removal # while read line do display "$line" echo "Remove this entry (y/n)? c" read answer < /dev/tty if [ "$answer" = y ] then break fi done < /tmp/matches$$ rm /tmp/matches$$ if [ "$answer" = y ] then if grep -v "^$line$" $PHONEBOOK > /tmp/phonebook$$ then mv /tmp/phonebook$$ $PHONEBOOK echo "Selected entry has been removed" else echo "Entry not removed" fi fi
The rem
program collects all matching entries into a temporary file. If the size of the file is zero, no match was found and an appropriate message is issued. Otherwise, for each matching entry, the program displays the entry and asks the user whether that entry is to be removed. This provides reassurance to the user that the entry the user intends to remove is the same one that the program intends to remove, even in the single match case.
After a y
has been typed to the program, a break
command is executed to exit from the loop. Outside the loop, the program tests the value of answer
to determine how the loop was exited. If its value is not equal to y
, then the user doesn't want to remove an entry after all (for whatever reason). Otherwise, the program proceeds with the removal by grep
ing out all lines but the desired one (and here the pattern specified to grep
is made to match only entire lines by anchoring it to the start and end of the line).
# # Change an entry in the phone book # name=$1 # # Get matching entries and save in temp file # grep "$name" $PHONEBOOK > /tmp/matches$$ if [ ! -s /tmp/matches$$ ] then echo "I can't find $name in the phone book" exit 1 fi # # Display matching entries one at a time and confirm change # while read line do display "$line" echo "Change this entry (y/n)? c" read answer < /dev/tty if [ "$answer" = y ] then break fi done < /tmp/matches$$ rm /tmp/matches$$ if [ "$answer" != y ] then exit fi # # Start up editor on the confirmed entry # echo "$linec" | tr '^' ' 12' > /tmp/ed$$ echo "Enter changes with ${EDITOR:=/bin/ed}" trap "" 2 # don't abort if DELETE hit while editing $EDITOR /tmp/ed$$ # # Remove old entry now and insert new one # grep -v "^$line$" $PHONEBOOK > /tmp/phonebook$$ { tr ' 12' '^' < /tmp/ed$$; echo; } >> /tmp/phonebook$$ # last echo was to put back trailing newline translated by tr sort /tmp/phonebook$$ -o $PHONEBOOK rm /tmp/ed$$ /tmp/phonebook$$
The change
program allows the user to edit an entry in the phone book. The initial code is virtually identical to rem
: it finds the matching entries and then prompts the user to select the one to be changed.
The selected entry is then written into the temporary file /tmp/ed$$
, with the ^
characters translated to newlines. This “unfolds” the entry into separate lines for convenient editing. The program then displays the message
echo "Enter changes with ${EDITOR:=/bin/ed}"
which serves a dual purpose: It tells the user what editor will be used to make the change while at the same time setting the variable EDITOR
to /bin/ed
if it's not already set. This technique allows the user to use his or her preferred editor by simply assigning its name to the variable EDITOR
and exporting it before executing rolo
:
$ EDITOR=vi; export EDITOR; rolo
The signal generated by the Delete key (2) is ignored so that if the user presses this key while in the editor, the change
program won't abort. The editor is then started to allow the user to edit the entry. After the user makes his changes, writes the file, and quits the editor, control is given back to change
. The old entry is then removed from the phone book with grep
, and the modified entry is converted into the special internal format with tr
and tacked onto the end. An extra newline character must be added here to make sure that a real newline is stored in the file after the entry. This is done with an echo
with no arguments.
The phone book file is then sorted, and the temporary files removed.
# # list all of the entries in the phone book # IFS='^' # to be used in set command below echo "-----------------------------------------------------" while read line do # # Get the first and last fields, presumably names and numbers # set $line # # display 1st and last fields (in reverse order!) # eval printf ""%-40.40s %s\n"" ""$1"" ""${$#}"" done < $PHONEBOOK echo "-----------------------------------------------------"
The listall
program lists all entries in the phone book, printing just the first and last lines of each entry. The internal field separator characters (IFS
) is set to a ^
, to be used later inside the loop. Each line from the phone book file is then read and assigned to the variable line
. The set
command is used to assign each field to the positional parameters.
The trick now is to get the value of the first and last positional parameters because that's what we want to display. The first one is easy because it can be directly referenced as $1
. To get the last one, you use eval
as you saw in Chapter 13, “Loose Ends.” The command
eval echo ${$#}
has the effect of displaying the value of the last positional parameter. The command
eval printf ""%-40.40s %-s\n"" ""$1"" ""${$#}""
gets evaluated to
printf "%-40.40s %-s " "Steve's Ice Cream" "${4}"
using the entry shown previously as the example, and then the shell rescans the line to substitute the value of ${4}
before executing printf
.
Now it's time to see how rolo
works. We'll start with an empty phone book and add a few entries to it. Then we'll list all the entries, look up a particular one, and change one (using the default editor ed
—remember that the variable EDITOR
can always be set to a different editor and then exported). To conserve space, we'll show only the full menu that rolo
displays the first time.
$ PHONEBOOK=/users/steve/misc/book $ export PHONEBOOK $ rolo Start it up /users/steve/misc/book does not exist! Should I create it for you (y/n)? y Would you like to: 1. Look someone up 2. Add someone to the phone book 3. Remove someone from the phone book 4. Change an entry in the phone book 5. List all names and numbers in the phone book 6. Exit this program Please select one of the above (1-6): 2 Type in your new entry When you're done, type just a single Enter on the line. >> Steve's Ice Cream >> 444 6th Avenue >> New York City 10003 >> 212-555-3021 >> Steve's Ice Cream has been added to the phone book Would you like to: ... Please select one of the above (1-6): 2 Type in your new entry When you're done, type just a single Enter on the line. >> YMCA >> 973-555-2344 >> YMCA has been added to the phone book Would you like to: ... Please select one of the above (1-6): 2 Type in your new entry When you're done, type just a single Enter on the line. >> Maureen Connelly >> Hayden Book Companu >> 10 Mulholland Drive >> Hasbrouck Heights, N.J. 07604 >> 201-555-6000 >> Maureen Connelly has been added to the phone book Would you like to: ... Please select one of the above (1-6): 2 Type in your new entry When you're done, type just a single Enter on the line. >> Teri Zak >> Hayden Book Company >> (see Maureen Connelly for address) >> 201-555-6060 >> Teri Zak has been added to the phone book Would you like to: ... Please select one of the above (1-6): 5 ----------------------------------------------------------- Maureen Connelly 201-555-6000 Steve's Ice Cream 212-555-3021 Teri Zak 201-555-6060 YMCA 973-555-2344 ----------------------------------------------------------- Would you like to: ... Please select one of the above (1-6): 1 Enter name to look up: Maureen ------------------------------------- | Maureen Connelly | | Hayden Book Companu | | 10 Mulholland Drive | | Hasbrouck Heights, NJ 07604 | | 201-555-6000 | | o o | ------------------------------------- ------------------------------------- | Teri Zak | | Hayden Book Company | | (see Maureen Connelly for address)| | 201-555-6060 | | | | o o | ------------------------------------- Would you like to: ... Please select one of the above (1-6): 4 Enter name to change: Maureen ------------------------------------- | Maureen Connelly | | Hayden Book Companu | | 10 Mulholland Drive | | Hasbrouck Heights, NJ 07604 | | 201-555-6000 | | o o | ------------------------------------- Change this person (y/n)? y Enter changes with /bin/ed 101 1,$p Maureen Connelly Hayden Book Companu 10 Mulholland Drive Hasbrouck Heights, NJ 07604 201-555-6000 2s/anu/any Change the misspelling Hayden Book Company w 101 q Would you like to: ... Please select one of the above (1-6): 6 $
The only function not tested here is removal of an entry.
Hopefully this example has given you some insight on how to develop larger shell programs, and how to use the many different programming tools provided by the system. Other than the shell built-ins, rolo
relies on tr
, grep
, an editor, sort
, and the standard file system commands such as mv
and rm
to get the job done. The simplicity and elegance that enable you to easily tie all these tools together account for the deserved popularity of the Unix system.
See Appendix B for more information on downloading the rolo
programs.
Chapter 15, “Interactive and Nonstandard Shell Features,” introduces you to interactive features of the shell and two shells that have some nice features not found in the POSIX standard shell.
3.145.179.252