Demystifying Printers and Printing
I am going to wander into frightening territory. Over the years, I have seen several estimates regarding system administrators and printers. One figure (which had a fairly strong argument backing up the numbers) put printer administration at around 25 percent of a system administrator's time. If you think printers are fun, try wandering by your tech support people's cubicles (assuming you are not one yourself) and waiting for a printer call to come through. You shouldn't have to wait too long. Next, note the look on their faces when they're told it's a printer problem.
I've sort of avoided dealing with printers myself. The reason is that, like most system administrators, I also hate dealing with printers. In this article, I will show how you can make printer hell closer to printer heaven. In doing this, I'll walk you through the basics of what really happens when you print. Armed with the nuts and bolts of printing, we will then look at some clever tools and techniques that add unprecedented flexibility to your everyday printing needs.
Linux offers us a number of printing options. We can do text, Postscript, local printers, as well as lpd remotes. If we want to, we can even create queues that direct printing to our co-workers' Windows 9x printers, or provide Windows 9x users with Linux print services (using Samba). One printer is mostly okay, but add a few more, and you've got yourself a real handful. What Linux does do here is provide a wide range of options for dealing with all this diversity.
Let's go back to the beginning (well, almost) and see how this whole thing actually works, starting with your old friend, the parallel printer.
Basic PC architectures usually have a single parallel port. A printer on that port would actually be connected to /dev/lp0. If your PC has more than one parallel connector (or if you added a printer card), the second and third ports would be /dev/lp1 and /dev/lp2, respectively. Now, let's pretend you have a basic, run-of-the-mill printer attached the first parallel port. Printing can be as simple (or as strange) as this:
# echo "This is a test." > /dev/lp0 # echo "Please ignore this print job." > /dev/lp0 # echo "There is nothing of value here." > /dev/lp0 # echo "^L" > /dev/lp0
The last line, with the ctrl-L, means "send a form feed to the printer to eject the page." Now the output of this silly job looks like this:
This is a test. Please ignore this print job. There is nothing of value here.
This is what my printer configuration tool refers to as "stair stepping of text." You may have also heard it referred to as the "staircase effect." What happens is that my printer, a LaserJet 5L, in this case, expects either PCL (its native printer command language) or DOS-style text. That means carriage returns and line feeds at the end of each line, unlike Linux files, which default to simply line feeds. In any case, this is a nice display of the need for filters.
Let's take exactly the same lines of (non) information (minus the echo "^L" line) and create a file called testfile. I used pico for this, but you can use whatever editor makes you happy. If we simply cat the file to the printer as before (with cat testfile > /dev/lp0), the results are the same. Let's build a filter. Here's what mine looks like:
#!/bin/bash echo -ne \\033\&k2G cat echo -ne \\f
Not much to it, is there? The first echo line sends the command that tells my LaserJet to convert Linux line feeds to carriage return followed by a line feed, an octal escape character followed by the printer control sequence &k2G. This is an HP code, so your printer may require a different code. The next line simply takes the input you gave it and passes it through, unaltered. The final line sends a form feed to eject the page. I called the file dosfilter, moved it to /usr/local/bin, and made it executable.
# cp dosfilter /usr/local/bin # chmod 755 dosfilter
Now let's resend the job and use the filter:
# cat testfile | /usr/local/bin/dosfilter > /dev/lp0
Now my text comes out looking normal, one line after the other, and properly aligned. Pretty neat, but in Real LifeTM, we usually don't send jobs to printers in this way. We create printers on the system and send jobs by spooling them. Let's create a spooler definition called lptest. Do this by editing /etc/printcap and adding the following information. Keep in mind that the printcap file is quite picky about how it is laid out. That's why the guide you received with your Linux system (assuming that you bought a nice boxed set) tells you to use printtool, or something like it. Learning to do it the hard way is more fun in the long run. Later, we'll use nice tools. (Rightthe /etc/printcap file.) It looks like this:
lptest:\ :sd=/var/spool/lpd/lptest:\ :mx#0:\ :sh:\ :lp=/dev/lp0:\ :if=/usr/local/bin/dosfilter:
Here's what all that means. The first line is simply a printer name. If I send a job to a printer with the lpr command, I would specify this queue name. The next line refers to the spool directoryin other words, where all the information related to the current job goes. This is a directory under the /var/spool/lpd directory (by default), but it could conceivably go anywhere you like. /var/spool/lpd is just convention. I like to use a directory name that is the same as the queue name (I'm funny that way).
mx refers to the maximum file size you will allow to be sent to the printer. A zero means unlimited. The next line (:sh:\) means "suppress header page". Since this is a physically connected device, we have the lp option, which defines the parallel device itself (/dev/lp0 since I have only one parallel port). The last line refers to my input filter, the dosfilter we created earlier.
To make this all work, you need only create the spool directory (/var/spool/lpd/lptest) and send your job to the printer.
# mkdir /var/spool/lpd/lptest # lpr -Plptest testfile
Did you get an extra, unwanted form feed with your job? If so, modify your dosfilter and remove (or comment out) the form feed line. Another way, if you do not want to modify your dosfilter, would simply be to add another parameter to the /etc/printcap definition, one that tells the printing subsystem to "suppress formfeed." The line would look like this:
The options sh and sf are booleans. Their presence in the /etc/printcap file means "true." It's up to you to decide where these things are better located (in printcap or in a filter). For a more complete list of printcap options, you can use man and check out the page.
# man printcap
By default, when you send a job to the printer with the lpr command, it uses a queue definition called lp (as opposed to lptest, or whatever name you gave your queue). If the queue was simply named lp, the only command you need to print is this:
# lpr printfile_name
That's why we used the long form of the command.
# lpr -Pprint_queue_name printfile_name
If you always print to a specific printer, you can add the PRINTER environment variable to your $HOME/.bash_profile.
PRINTER=lptest ; export PRINTER
Now, after you log in, all you have to do (assuming that you want to print to lptest) is type the first, simpler version of the print command (lpr printfile_name).
Now let's look at printing to a remote Linux (or Unix) printer. Let's create a printer called faraway on our current machine, called nearlinux. The printer (the lptest we created earlier) is on another machine called farlinux. Here's the /etc/printcap entry for nearlinux:
faraway:\ :sd=/var/spool/lpd/faraway:\ :mx#0:\ :sh:\ :rm=farlinux:\ :rp=lptest:
There are only a couple of really new things here. For starters, the lp line that defines the physical location of my printer is gone. Now we have an rm option, which refers to the remote hostname (the host to which the printer is connected). The last line, :rp=lptest:, defines the remote printer's name on that system.
The only catch here is that farlinux has to let me print. This is done by editing the file /etc/hosts.lpd and adding the hosts or host IP addresses of the machines that are allowed to print. My hosts.lpd file has these entries:
192.168.22.2 192.168.22.3 nearlinux
It is probably a good idea to restart your lpd daemon after making this kind of change. One way to do this is simply to do a ps ax | grep lpd and kill the current lpd process. The second (which may vary slightly from system to system) is to use these commands:
# /etc/rc.d/init.d/lpd stop # /etc/rc.d/init.d/lpd start
On some releases, you can wrap this up in one command by using restart instead of stop followed by start. Now, from my machine, I can send a really important file to my remote Linux machine for printing:
# /usr/games/fortune -l | lpr -Pfaraway