An lp Enhancement
Leor Zolman
Try as I might, I couldn't think of an appropriate title
for this
article that wasn't almost as complicated as the software
it describes.
The UNIX printer scheduler subsystem is a hairy beast,
as idiosyncratic
and crash-prone as it is powerful and flexible. However,
even with
its "out of the box" support for numerous
physical printers
and collective printer "classes" (most useful
in high-volume
installations), lp fails to provide a convenient solution
to
certain needs. In this article, I will show how I built
upon the standard
lp system's functionality to provide a friendly end-user
interface
for selection of printer paper types in a large multi-printer
installation.
A Matter of Real Estate
As has been the case with all the shell scripts in my
Sys Admin
articles so far, this code was written to solve a real-world
problem
encountered in the course of administering R&D Publications,
Inc.'s
UNIX system (Xenix, actually, but basically vanilla
System V).
We were about to move a few blocks down the street into
new accommodations.
In the old building (a relatively compact two-floor
office space),
all of the system printers were located in a single
central area.
Our printer repertoire consisted, at the time, of two
dual-tray Panasonic
lasers loaded with various publication-specific letterhead,
a Kyocera
laser for general-purpose plain-paper applications,
an HP LaserJet
II with high-capacity sheet feeder for large-volume
work, and several
dot-matrix printers for multi-part invoice forms and
peel-off labels.
Since the building was small, it was relatively easy
for everyone
to stroll over to the printer area to get their printer
output. Each
software application always sent its output to the particular
logical
printer (a specific tray on one of the physical printers)
assigned
to that application. Even general plain-paper output
was always sent
to the same specific printer tray.
Taking a printer off-line for repair usually meant that
someone was
inconvenienced for a while. We would work around the
missing printer
by swapping paper trays before and after certain documents
were printed
or by waiting for the missing printer to return. If
a critical printer
died at some particularly inopportune moment, the system
administrator
had to re-route the output to a different device by
changing the driver
scripts for all applications that relied on that printer.
This was
an awkward process, at best.
I was on the committee that designed the departmental
layout for R&D's
new building, which, unlike the previous bulding, was
broken up into
several separate areas. Immediately it became clear
that our old one-to-one
task/printer mapping scheme would fall flat on its typeface
in the
new workspace. No single location for the printer stable
would be
convenient enough for everybody in the company, or even
for half of
the company. While some letterhead forms would be needed
by only a
single department, other forms might be needed by two
or more departments
located at opposite ends of our space. And, of course,
every department
needed at least one centrally located plain-paper printer.
Those requirements resulted in our placing the order
for two more
dual-tray lasers, one more LaserJet and one more dot-matrix
printer.
With all these printers, it would have been difficult
to remember
which lasers had which kinds of letterhead. As it had
always been
our goal to shield most of the employees from the technical
details
of our system, we did not want to have to educate everyone
on the
intricacies of the lp command just so they could get
their
output to go where needed. In fact, since most of our
software was
menu-driven, few employees had ever had any need to
know about the
lp command at all.
The Ideal Scenario
Brainstorming, I realized that what our users really
needed was to
be able to select a particular paper type from a simple
menu, and
get their output to automatically appear on the printer
closest
to their normal work area that happens to support the
required
form. Jeff in the Marketing Department, for example,
ought to be able
to print the letter he just composed on "plain
paper," without
having to worry about specifying which plain-paper-equipped
printer to use. In the Marketing Department there is
only one plain
paper tray, and that is where the output should be routed
by default.
On the other hand, when Jodi in the Customer Relations
Department
asks for her letter to be printed on plain paper, the
job should
go to the plain paper printer in her department, by
default,
and not to the Marketing Department's plain paper printer.
For this
arrangement to work, the system must:
have enough information to associate each user with
a particular department,
know about all the kinds of letterhead available in
each department, and
know how to map "generic" letterhead requests
into the appropriate lp command arguments (specific
UNIX printer
id and possibly one or more driver options) that result
in selection
of the appropriate input tray on the appropriate specific
physical
printer.
For greatest convenience, the system should also know
what to do when a user is either logged in on a terminal
located in
a department different from her usual one, or, in requesting
a report,
realizes that she will be in a different department
when the report
prints out and wants to pick the output up in that department.
Therefore,
paper types must always be selected in the context of
a particular
department, and the user must be given the option to
specify a
departmental context as well as the paper type.
While most of R&D's users make their paper-type
selections from "bullet-proof"
interactive report programs invoked from the company's
menu system,
there are certain advanced users who do use the lp command
occasionally for personal documents, and need to retain
that capability
while still gaining the benefits of the new routing
mechanism. Also,
many of the report programs invoked from the menus are
written non-interactively
and need a way to send data to the printer without requiring
users
to make a paper type selection but nevertheless retaining
the proximity-related
"smarts" described above. For these reasons,
I wrote the llp
command (Listing 1) to recognize departmental routing
and still serve
as a system-wide (and non-interactive) replacement for
the standard
UNIX lp command.
For the rest of this article, I'll use the term "llp
system"
to refer to the entire set of scripts and configuration
files described,
while llp written alone in the monospaced font will
refer to
the single command of that name.
The llp syntax replaces lp's usual -dxxx -oxxx
options with simple paper type options. In all other
ways llp
works just like lp. You can pipe to llp, give it other
lp options (such as -n), and cancel jobs after they
are submitted. We think of llp as an lp "front
end,"
which converts a paper-type specification into the appropriate
lp
options (based upon the llp system configuration tables).
As a "front-end,"
llp simply passes all other command line arguments to
lp,
untouched.
The *.map System Configuration Tables
A set of configuration files describes the various relations
needed
by the llp system. These files isolate implementation-dependent
information
from the actual code.
A single master configuration file relates the llp system's
logical
printers and paper types to the physical device names
and printer
options required by the UNIX printer spooler. This file,
master.map
(Listing 7), associates each UNIX printer device (2nd
column) with
a symbolic name (first column), and describes the paper
type contained
in each of up to two paper trays per device (columns
3 through 6.)
With the llp system, to re-route a disabled printer's
jobs to another
similarly-configured printer, the system administrator
changes only
the assignments in master.map. Users in the working
printer's
location would see no change. This re-routing will only
work, however,
if there are two physical printers set up with precisely
the same
combination of letterhead in their trays. If the disabled
printer
is the only one using a particular kind of letterhead,
the users must
still physically shuffle the physical paper trays.
Each Department known to the system must be assigned
a reference number,
a full descriptive name, and a short name. This short
name will be
assigned to every user's DEPT environment variable (in
their
startup .profiles), thereby associating users with their
native
departments. The configuration file dept.map (Listing 8) contains
this information in the first three fields. The final
field is a single
(unique) upper-case character used in the interactive
menus to select
the department context. (I didn't just scan the department
description
for this character because there could easily be two
department
descriptions that begin with the same letter).
The configuration file paper.map (Listing 9) holds details
about the available forms. Each line in the file (except
for blank
lines and comments) defines a particular form type.
Several
printers may be loaded with the same form type (e.g.,
particular letterhead),
but paper.map always contains only one line per type.
The first
field contains the "standard" form name, corresponding
to
columns 3 and 5 in master.map. The second field is a
full description
of the form. The third field contains a list of acceptable
"aliases"
for the form, delimited by colons. (Note that colons
also precede
and follow each list. These colons make it easier to
grep without
composing patterns to match the special cases at the
beginning and
end of a list). No alias may be used more than once
in the entire
table. The final field of each line is a list of upper-case
characters
denoting the top-level command scripts that accept the
paper type
defined on that line. If the code letter corresponding
to a specific
command (llp:L, getptr:G, gethead:H and getlbl:B)
appears in the list, then that paper type is accepted
by the corresponding
command. The scripts generate an error message if a
user attempts
to select a paper type which is not supported by the
target command.
The last configuration file, proximity.map (Listing 10), provides
the "intelligence" to route jobs to the nearest
available
devices. Each line of this file contains one combination
of form type
and department number in the first two fields. The third
field contains
the name of the logical printer device to be associated
with the combination
in the first two fields. Thus, on a case-by-case basis,
each paper
type for each department is assigned to the particular
logical printer
best suited to the department's location. If the department
itself
hosts a suitably-equipped printer, the choice is obvious.
Otherwise,
one would probably pick the closest printer that does
support the
needed paper type.
The Shared Utility Scripts
The four top-level llp system commands (implemented
in three scripts,
one of which has two links) share much of their code.
Rather than
duplicate the common portions in each script, I've taken
advantage
of a shell programming technique known as "sourcing."
Like
the C language's #include construct, shell sourcing
allows
the same sections of code to be used in many programs.
Because the sourced code must use environment variables
to pass values
back to the "calling" commands, the common
scripts must execute
in the environment of the invoking commands. Invoking
the common code
in the usual way would fork to a subshell. To avoid
creating a subshell,
sourced scripts are invoked with a command of the form:
. $PTRDIR/utility-script
The leading period executes the utility script within
the current shell's environment.
This sourcing technique is used twice within each command
script.
Each command sources common code to perform common initializations
and rudimentary validation on the optional command-line
arguments
(initmap.sh, shown in Listing 5). The commands source
a separate
file to validate the final department and form types,
perform the
proximity mapping, do the master table lookup, and construct
the actual
output string (proxmap.sh, shown in Listing 6).
Figure 1 shows the input-output characteristics of the
initmap.sh
script. Similarly, Figure 2 shows the input-output characteristics
of proxmap.sh. Each of these scripts uses a combination
of
environment variables and configuration tables as input,
and produces
output in the form of one or more environment variable
settings.
A Bonus Feature
Since I had free reign while writing this system, I
included a "Soft
Copy" feature designed specifically to help System
Administrators
test new applications. When activated, any print job
submitted via
the llp command can be directed to a file instead of
the print
queue. (Output can also be directed to both a file and
the
print queue.) In our system environment, most of the
final output
from the menu-driven report generation scripts is generated
using
llp, so this feature saves a lot of paper and shoe wear
while
escorting shell scripts through their final production
testing.
The setsoft command configures the Soft-Copy feature;
however,
the command is only available to users who have assigned
Y
to the exported environment variable SOFTCOPY. No, environment
variables aren't user friendly, but I figure that users
who don't
understand environment variables probably wouldn't have
much use for
this feature anyway.
The Implementation
The complete code for the llp system consists of five
major shell
commands, two utility scripts (sourced by the major
commands), and
four configuration files. Two of the major commands
are links to the
same script file.
The major shell commands are:
llp -- To be used instead of the standard lp command
setsoft -- Configure Soft-Copy feature
getptr -- Prompt for paper type within a shell script
gethead -- Prompt for letterhead type from a shell script
(linked to getptr)
getlbl -- Prompt for a label printer from a shell script
While llp and setsoft are designed to be run from the
system prompt, the last three commands are meant for
use within a
shell command. Their standard output is designed to
be captured
within back-ticks and fed to the lp command as part
of the
option list.
The llp Command
The llp script (Listing 1) supports the logical paper
types
whose lines contain an L in the last field of the paper.map
file. In our implementation, those paper types happen
to be:
plain -- Plain white laser paper (laser)
letter1 -- C Users Journal letterhead (laser)
letter2 -- Windows/DOS Developer's Journal letterhead (laser)
letter3 -- R&D Publications, Inc. letterhead (laser)
kyocera -- plain paper on the Kyocera laser printer
Certain other paper/printer types, such as the sheet-feeder-equipped
HP lasers and the label printers, are not recognized
by llp
because those printers are typically used only for dedicated
menu-driven
applications. The interactive paper type selection scripts
described
later on, however, do recognize some of those other
devices.
There are three distinct departmental areas supported
in our llp system
configuration. Two departments, Marketing (mktg) and
Periodicals (per)
share the same printers, which are located in the Marketing
Department.
To effect this sharing, part of the initialization sequence
(in the
initmap.sh script) maps the Periodicals Department (per)
into
the Marketing Department (mktg). As far as the rest
of the llp system
is concerned, the Periodicals Department doesn't exist
(no offense
intended, guys!). Two additional departments, Internal
Services (is)
and Customer Relations (cr), each have their own set
of printers,
but no department is set up with the same exact set
of forms
as any other department. If the departments all had
the same forms
available, there wouldn't be much for this system to
do except
handle re-routing of out-of-service printers.
All users have one or two environment variables defined
in their .profile
files relating to departmental printer configuration:
DEPT -- The user's native department (as per dept.map, field #2).
DEPTP -- The department whose printers the user is
actually closest to, if different from DEPT.
For most users, DEPTP is left undefined, since the printers
in their own department are the closest to their desks.
Occasionally,
however, an employee belonging to one department will
end up with
a desk physically closer to another department's printers.
For such
users, DEPTP is defined with the name of the department
whose
printers will be used, and overrides DEPT. In initmap.sh
(Listing 5), lines 19-20 adjust the DEPT variable based
on
the value of DEPTP. This arrangement allows other non-llp-system
software to process the user as a member of his/her
actual department
by sampling DEPT, if necessary.
If one of the System Administrators forgets to set DEPT
in
a new user's .profile, line 14 sets their department
to mktg
by default. (We created more accounts for people in
the Marketing
Department than in any other, so I picked that as the
default.)
Since the Periodicals Department shares the Marketing
Department's
printers, any DEPT value of "per" is changed
to "mktg"
in line 17.
The syntax for the llp command is:
llp [-paper_type] [other_lp_options...] [files...]
Any regular lp options (other than the destination
specification and any related form selectors) may be
used with llp.
If no file(s) are specified, llp reads standard input
(just
like lp). The paper_type may be specified in either
upper or lower case, and may be fully written out or
abbreviated to
any of the forms listed in the third field of the paper.map
file. These options make the command more user-friendly.
If the user
supplies no paper type, then the plain type is selected
by
default.
The script proxmap.sh (Listing 6) maps the paper type
into
the required corresponding lp command options. This
utility
script is the heart of the system. It sets the lpopts
environment
variable to -d followed by the precise UNIX printer
name (and
possibly a -o option if the output is for a non-default
paper
tray). In short, proxmap.sh builds the arguments which
must
appear on the standard UNIX lp command line.
The Soft-Copy feature is controlled by the SOFTCOPY
environment
variable. If this variable has a value of Y in the user's
environment,
then the Soft-Copy mechanism (as configured on a per-user
basis by
the setsoft command) is recognized by llp. Each user's
Soft-Copy settings, if any, are stored in the directory
$HOME/.Soft. setsoft
creates and initializes files in this directory the
first time it
is run by each user.
The .Soft directory contains two files, Soft.status
and Soft.file, symbolically named SOFTSTAT and SOFTFILE.
The SOFTSTAT file contains
the single letter F when llp output is
to be redirected into a file only;
the single letter B when llp output is
to go to both the redirected file and to the printer;
no text at all when llp output is to go directly
to the printer (with no special redirection).
If file output is enabled by the presense of one of
the recognized,
non-null SOFTSTAT values, then the SOFTFILE file must
contain the name of the file to be used for text collection.
This
file name may be assigned at any time by running the
setsoft
command. Each invocation of llp with Soft-Copy enabled
causes
the output to be appended to the currently specified
collection
file.
Lines 40-56 of the llp command (Listing 1) control the
Soft-Copy
mechanism. The current contents of the SOFTSTAT file
are read
into the SOFTCODE environment variable, and the collection
file name is read into the SOFTNAME variable. Lines
49-53 handle
a Soft-Copy status of B, issuing the appropriate sequence
of
piped commands based on whether the llp command's input
is
coming from the standard input or from files named on
the command
line. If the Soft-Copy status is F, then no lp command
needs to be generated, and all of llp's input (standard
or
otherwise) is simply appended onto the current text
collection file
(line 54).
Finally, lines 58-66 handle the case of output going
to the lp
command alone.
Prompting for a Printer Selection
While the llp and setsoft commands are invoked directly
from the command line as standalone programs, the remaining
scripts
in the llp system are intended exclusively for interactive
use within
other shell scripts. Both of these scripts (getptr,
its link
gethead, and getlbl) prompt the user with a menu of
printer selection options (sending all output to the
error stream),
and return their results on the standard output stream
for collection
within back-quotes in the calling shell script. For
example, if we
want a user to select the printer to which some report
output should
be sent, the script might appear as:
destination=`getptr p`
runreport.sh | lp -d$destination
If runreport.sh writes to standard output, then
its output will go to the printer selected by the user
while running
the getptr command. The p argument to getptr
told getptr what the default paper type was supposed
to be
(plain); the user may have either accepted that default
paper type
or superseded it with an explicit paper selection, departmental
change
or both. Figure 3 shows what users see on their terminal
when the
command getptr p is invoked. This menu text is written
to standard
error so that it appears on the user's terminal, since
standard output
is used only to return a printer designation to the
invoking shell
script.
Note that on the last line of Figure 3, the default
of P appears
in square brackets. This default allows the user to
simply press the
Return key to select the default paper type.
To select a different paper/printer type, the user need
only enter
the appropriate code from the menu shown. Note also
that the name
of the user's default department is shown at the top
of the figure.
The user can ask to override this default by pressing
A.
Figure 4 illustrates the entire sequence that transpires
when getptr
p is invoked and the user wishes to specify a departmental
change.
In this example, the user first presses A to request
the departmental
change. The system responds with a PRINTER AREAS menu,
and
prompts for one of the area codes shown. The user presses
C,
for the Customer Relations Department. At that point,
a new printer
selection menu appears, this time based in the Customer
Relations
area, and finally the user presses Return to select
the plain
paper printer in the selected area.
The main loop of getptr/gethead (Listing 2, lines 28-74)
displays
the printer selection menu and processes keystrokes
until a paper
type selection (in $ptype) and department (in $DEPT)
have been selected. Then, proxmap.sh is sourced to perform
the proximity mapping (line 80). Finally, line 82 echos
the appropriate
UNIX printer name and options on the standard output.
The only difference between getptr and gethead is that
gethead accepts fewer paper types: only letterhead-style
forms
are offered. The two forms of this command are distinguished
by the
values assigned to the CODECHAR and WHAT environment
variables (Listing 2, lines 14-19.) WHAT controls the
wording
of the messages in the prompt text, and CODECHAR defines
the
letter that must appear in the last field of each line
in paper.map
to accept a paper type.
The getlbl script is a variation of getptr which has
been simplified to eliminate the paper type selection.
This variant
offers only a departmental context switch as a menu
option. In our
environment, this script is used to select between two
peel-off label
printers. Obviously, output meant for such 1-up continuous-form
labels
should not be sent to the regular laser printers, and
vice-versa.
To make labels fully distinct from the other paper types,
I gave the
label printers their own special selection script.
Configuring the Soft-Copy Feature
The final script in the package is setsoft (Listing 4), the
command to control the Soft-Copy feature for "sophisticated"
users and system administrators.
When invoked, setsoft shows the user's current Soft-Copy
status
and displays a menu of options. (See Figure 5 for a
sample run.) The
user may completely disable Soft-Copy, specify File-Only
mode,
or specify Both-File-and-Printer mode. The user may
also specify a
different file for the collection file. All setsoft
status
options persist from login session to login session,
so remember to
turn Soft-Copy off when you are done testing your application!
In Summary
This system is probably my favorite of all the administrative
tools
I've concocted over the years, because it builds almost
seamlessly
over an existing framework to extend the usefulness
of UNIX's standard
lp system, while actually reducing the potential for
end-user
confusion. Thanks to the llp system, we don't have to
put printers
for every kind of paper type in every department, and
our users don't
have to trek all over the building to get output on
a special type
of paper. Instead, the llp system allows us to optimize
the tradeoff
between printer supply and demand, based on the number
of available
printers and the geographical layout of a company's
departments. No
printers are "wasted" and every employee gets
reasonably convenient
service. And, this ability may be easily and cheaply
added to any
Xenix or UNIX system, with a few easily-maintained scripts
and configuration
files.
About the Author
Leor Zolman wrote BDS C, the first C compiler targeted
exclusively for
personal computers. Leor is currently an instructor
on UNIX topics for
Boston University's Corporate Education Center, a regular
contributor to
The C Users Journal and Sys Admin magazines, and "Tech
Tips" editor for
Windows/DOS Developer's Journal. His first book, Illustrated
C, was recently
published by R&D Publications, Inc. He may be contacted
at 74 Marblehead St.,
North Reading, MA 01864, or on Usenet/Internet as: leor@bdsoft.com.
|