Cover V02, I02
Figure 1
Figure 2
Figure 3
Figure 4
Figure 5
Listing 1
Listing 10
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6
Listing 7
Listing 8
Listing 9


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, (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 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 (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 (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 always contains only one line per type. The first field contains the "standard" form name, corresponding to columns 3 and 5 in 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, (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 (, 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 (, shown in Listing 6).

    Figure 1 shows the input-output characteristics of the script. Similarly, Figure 2 shows the input-output characteristics of 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 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 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, 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 (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 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 (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, 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` | lp -d$destination

    If 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, 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 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: