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