A Form Letter Facility for E-Mail
Robert Ward and Scott Graham
Whether electronic or paper-bound, forms contribute
a useful regularity
and structure to office communications. Forms are especially
to those who must supply detailed information about
subject or event. Thus, in a traditional paper-based
the bookkeeper is impressed into phone duty because
the phone staff
is out with the flu, a well-designed order form can
save the day.
By itemizing and structuring all the details that must
to complete an order, the form improves the bookkeeper's
getting the order right. Similarly, when users are faced
a technical problem or requesting a technical service,
electronic form can help them capture all the details
that the system
This article presents a general purpose form-letter
this system (built on top of the standard mail program),
can create easy-to-use electronic forms to facilitate
messages -- such as bug reports, resource requests,
While the frmlet package is useful to administrators,
entirely general and user-maintained. Users create and
own form templates. Each new or modified template is
checked for minimal correctness, and, if it passes,
added to the list of forms available for use.
To send a form, the user selects a form from a list
of available forms
(templates), fills in the blanks (using his or her standard
and exits. The frmlet application then mails the form
"owner," whose name is embedded in the form
While useful in its own right, frmlet is also a nice
of directory and menuing conventions and techniques
that are widely
used in various UNIX applications.
The forms package is two top-level scripts designed
to be embedded
in a menuing system. The frmlet script (Listing 1) manages
a session in which a user fills in and mails an existing
maintfrm script (Listing 2) manages a maintenance session,
during which a user could create, edit, or delete a
The frmlet script takes a directory as an argument.
should contain a set of related form templates. Passing
as a parameter allows the same script to be used for
categories of forms. The marketing department, the shipping
and the customer support department can each have their
set of forms. Widely used forms can be in a "enterprise-wide"
directory. Thus the script begins by testing for this
making the selected directory the current directory.
and MAILCMD variables simply isolate certain system-dependent
The frmlet script then calls a utility script, dirmnu
which does all the real work. The dirmnu script
builds a menu, based on what it finds in the current
returns the file name corresponding to the user's selection
Both top-level scripts invoke dirmnu, which we cover
The next line in frmlet
receiver = `fgrep "Send to:" \
$form_file | ....
exemplifies a common UNIX "trick" -- using
grep to extract data marked by some user-created convention.
In this instance, we decided that every template would
name of the user to whom it would be sent on a line
that begins with
the words "Send to:" (much like the mail header
Some of our lp administration scripts assume that lines
an exclamation mark are description lines for use in
a menu. In other
applications, we've used a leading hash mark (#) to
lines. Whatever the convention, the technique is the
same: keep the
data in the user-maintained file (where it's more apt
to be updated
properly), and extract it based on some simple pattern.
Note that we include head -1 in the pipe to insure that
get only the first occurrence of "Send to:"
and then use sed
to remove the mark, leaving just the recipient's name.
The script next performs some integrity checks, and
then invokes the
mailer. The options in this MAILCMD line work for our
but might need to be modified to work on yours. The
code assumes that
the mailer will invoke the editor and can "include"
in the mailer's buffer without modifying the original
file. The options
-i <include file>
The dirmnu Script
In many ways, dirmnu is the most interesting of the
This utility script builds a picklist from the file
names in a selected
directory, and then returns the filename corresponding
to the user's
selection on stdout. The dirmnu script is invoked with
arguments: a title (which will appear at the top of
a prompt (which will appear at the bottom of the picklist),
pattern (which is used to filter out unwanted files
from the picklist).
Normally, scripts write user-directed output to stdout.
however, is designed to be "backquoted" in
and thus must return only its result on stdout (which
redirected by the backquoting mechanism). Accordingly,
output is directed to stderr ( >&2 ). It might
be more technically
correct to direct the output to /dev/tty. Either stream
show on the terminal.
After echoing the title to the screen and checking for
an empty directory
(an error condition), dirmnu uses awk and pr to
format a list of files in columns. Our first draft used
a very simple
command in which pr actually did most of the work:
ls $pattern | awk '(print " " $0)' | \
pr -t -n\)2 -i20 -4 >&2h
The -t suppresses leading and trailing linefeeds;
the -n\)2 puts a two-digit number and a parenthesis
of each filename; the -i20 option sets tabstops; and
option organizes the output as four columns. We wouldn't
awk at all, except that we wanted a space between the
and the filename. The tiny awk script inserts a space
of each filename.
The more complicated version we ultimately used in Listing
the number of columns dynamically, based on the length
of the longest
filename. The script pipes ls's output into an awk script
that prints the length of the longest filename. It then
to compute the number of columns (all of our terminals
devices). The result is a parameter to the display formatting
After displaying the numbered picklist, dirmnu uses
little utility script, getnum (Listing 4), to get the
response. The getnum script takes two numbers and a
The numbers represent the range of valid responses.
getnum is designed to be backquoted, and sends all interactive
output to stderr. This utility will loop, continuously
the prompt, until the user enters a valid response.
If the user makes
too many mistakes, the menu will scroll off the top
of the display.
A stronger design would use curser positioning to always
prompt in the same location.
By using an awk script to check the input range, we
conversion, range checking, and type validation all
for the price
Finally, dirmnu uses the getnum result in a head/tail
pipe to select the line corresponding to the selected
file. This usage
of head and tail is almost a shell script idiom.
This version of dirmnu uses the filename for the corresponding
menu entry. For some applications (and users), filenames
too cryptic. If you expand your file convention to include
that indicates an embedded file description, you can
generate menus consisting of arbitrary descriptions.
grep "DESC:" $pattern | sed "s/DESC:/ /" | \
pr -t -n\)2 -i" "1
should produce a one-column menu where each item is
description line extracted from the corresponding file.
The code also
depends on another small utility script, askyn (Listing
which prompts the user for a response and repeats until
the user supplies
The Maintenance Script
Though long, maintfrm (Listing 2) is quite straightforward.
The option to this script selects one of three modes:
or delete. When a template is to be edited, it is first
a temporary file, and when the modifications are complete
the temporary file is copied over the original template
and then removed.
We used a little C utility, tmpname.c (Listing 5), to
a unique name for the temporary file. You could get
a similar result
The $$ method, however, isn't guaranteed to produce
a unique file -- tmpname is. For example, if invoked
process number 101, this script would unintentionally
template named FORM101. (See Leor Zolman's article in
1.4 [Nov/Dec 1992] for more information on tmpname.)
makes use of the standard library function tempnam,
might wish to examine the man page for that.
Like frmlet, this script uses dirmnu to generate a picklist
of existing forms.
Both the create and edit options "syntax check"
the new form
template before installing it. Each option uses grep
for the pattern "Send To:". If this pattern
is missing, the
frmlet script won't know how to address a filled-in
notifies the user that a "Send To:" is required
askyn (Listing 6) to see if it should continue.
To ensure that the temporary file gets properly deleted,
traps all significant interrupts.
While this forms system offers significant benefits
to system administrators,
it is also general enough to be interesting to users.
is in itself an advantage to the administrator -- if
are already familiar with the forms system, they are
more likely to
use it when a system-related need arises.
In many ways, this version is still a prototype. We've
addressed the skeleton of the application -- the user
issues remain unsolved. How do you make it easy to fill
in the blanks?
How do you partition the forms into related sets? Where
does it fit
into the existing menu system?
Even so, we've installed it as is. In classic testimony
to the effectiveness
of the UNIX utility set, frmlet yields a surprising
of functionality from a tiny amount of code.
About the Authors
Robert Ward is president of R&D Publications and senior
editor of Sys Admin. He is the author of Debugging C, an
scientific debugging, and co-author, with William Smith,
Custom Controls. He has done consulting work in software
and data communications and holds a Master's degree
in Computer Science
from the University of Kansas.
Scott Graham received a BS degree in computer science,
engineering, and mathematics from Graceland College
in Lamoni, IA.
He is a graduate student in Computer Science at the
University of Kansas
and is a Programmer/Analyst for R&D Publications.