Chapter 14. Rolo Revisited

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.

Design Considerations

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

#
# 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.

add

#
# 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.

lu

#
# 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

#
# 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 |.

rem

#
# 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 greping 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

#
# 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.

listall

#
# 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.

Sample Output

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.

Exercises

1:

Modify rolo so that upper- and lowercase letters are not distinguished when doing a lookup in the phone book.

2:

Add a -m command-line option to rolo to send mail to the person who follows on the command line. Have rolo look up the person in the phone book and then look for the string mail:mailaddr in the matching entry, where mailaddr is the person's mail address. Then start up an editor (as in change mode) to allow the user to enter the mail message. When the editing is complete, mail the message to the user. If no mail address is found in the phone book, prompt for it.

Also add a mail option to the menu so that it can be selected interactively. Prompt for the name of the person to send mail to.

3:

After adding the -m option, add a -f option to specify that the mail message is to be taken from the file that follows on the command line. So

rolo -m tony -f memo

should look up tony and mail him the contents of the file memo.

4:

Can you think of other ways to use rolo? For example, can it be used as a small general-purpose database program (for example, for storing recipes or employee data)?

5:

Modify rolo to use the following convention instead of the exported PHONEBOOK variable: the file .rolo in each rolo user's home directory contains the pathname to that user's phone book file, for example:

$ cat $HOME/.rolo
/users/steve/misc/phonebook
$

Then add an option to rolo to allow you to look up someone in another user's phone book (provided that you have read access to it). This option should be added to the command line (as a -u option) as well as to the menu. For example,

$ rolo -u pat Pizza

would look up Pizza in pat's phone book, no matter who is running rolo. The program can find pat's phone book by looking at .rolo in pat's home directory.

6:

What happens with rolo if the user adds an entry containing a ^ or [ character?

7:

Add a –s (send) option to rolo to mail a rolodex entry to a specified user. So

$ rolo –s tom pizza

should send the rolodex card entry for pizza to the user tom.

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

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