Using a Printer from the Terminal
Using the command line to interact with a printer is surprisingly straightforward. You configure your printer by IP address, and then use it. Simplicity itself. Surprisingly, I’m even finding it more convenient than fighting with GUI printer setup systems. It feels like I have a lot more control, and direct access to a lot more useful information. Not to mention it opens the door to using my printer as an output for scripts or commands!
Assign a static IP address to the printer
Before we configure our computer to talk to the printer over the command line, the first thing you should do is configure your printer with a static IP address. While this step isn’t strictly necessary to get things working, if you skip it, things will eventually break. We’ll be configuring our printer using its IP address, and so if the printer is on DHCP there is always the chance that its IP address may change, which will break things.
I can’t go into the details on how to set a static IP for your printer, as it will vary from device to device. In general, you’ll want to go into your printer’s network settings, and manually set its IP address in there. You should also log into your router and ensure that the IP address you gave the printer is not in the range of addresses that your router can assign for DHCP. If you don’t do this, your router may assign a different device this IP address as well, which can lead to some very annoying, and very difficult to diagnose, network problems. I typically configure my router to not use address greater than 192.168.x.199 for DHCP, which leaves plenty of safe addresses for static assignments.
Install the necessary packages
Your Linux distribution may or may not have printing-related packages
pre-installed. If it doesn’t, you’ll need to install them. There are two common
systems used for printing, the original lpd
print daemon, or the more modern
CUPS (Common Unix Printing System). We’ll use CUPS here, as this is readily
available on most Linux distributions, as well as BSDs.
On Debian, the necessary packages can be installed using,
$ sudo apt install cups cups-browsed
Additionally, if you are using an older device that doesn’t support driverless
printing, you can install the printer-driver-all
package to get an assortment
of drivers, one of which will hopefully work for you. I’m not going to go into
drivers in this post, though; my focus is strictly on driverless printers.
Configuring your Printer
Once you have the necessary packages installed, you’ll need to configure the environment for printing and add your printer. This can all be done from the command-line using a variety of utilities that were installed alongside CUPS.
These tools are,
lpadmin(8)
– Used to add and configure printerslpstat(8)
– Used to view CUPS status informationlpoptions(8)
– Used to configure the default options for a printerlp(1)
– Used to print files
First off, you’ll want to confirm that CUPS is running. You can do this using
the lpstat
command, with the -t
flag to make it show all available
information.
$ lpstat -t
scheduler is running
Next, you need to add your network printer. You can do this via its IP address. If you need to look it up, the printer will usually have this information within its network settings.
Once you have the address, you can add the printer using lpadmin
as root,
# lpadmin -p <printer_name> -E -v ipp://<ip_address>/ipp/print -m everywhere
The -p
flag in the above command specifies the “name” of the printer. This is
the name you will use to specify the printer in future commands, and is also
the name that will appear in graphical print dialogs. The -E
flag enables the
printer and sets it up to accept new jobs. The -v
flag specifies the URI of
the printer (i.e., where it is on the network), and the -m
flag specifies the
driver; here we use the “everywhere” flag, which specifies driverless printing.
Now, you should be able to run lpstat -t
again and see the printer you added
on the list,
$ lpstat -t
scheduler is running
device for <printer_name>: ipp://<ip_address>/ipp/print
<printer_name> is accepting requests since ...
printer <printer_name> is idle. enabled since ...
Finally, we want to set this printer as the default. There are several different
ways to do this, however the simplest way is to use an environment variable. When
you use lp(1)
, it will first examine the PRINTER
environment variable, before checking
the default configured using lpoptions(8)
, and the finally the default configured by
lpadmin(8)
.
To set the printer as your default, add this line to your shell’s profile/rc file,
export PRINTER=<printer_name>
You can also run this command manually in your current terminal to temporarily
change the default printer. If you want to override the default for a single
command, most of the printer-related commands that need a specified printer
will allow you to state the printer by name, using the -d
flag. Only if one
is not specified will the command fall back on your default.
Issuing a print job
Now that we have a printer configured, let’s use it to actually print
something. The lp(1)
command is used to print files. With a default
printer configured, it’s simplest usage is,
$ lp <file_to_print>
It will also accept text on a pipeline, so you can pipe the output of a command directly to your printer if you want,
$ ls -lah | lp
Print options
Of course, the default print settings may not be what you want. So you are able to specify options for print quality, paper size, whether the document should be printed single or double sided, etc.
The options that are available will vary from printer to printer. The
lpoptions(8)
command can be used to list them for your default printer (or
-d
can be used to specify a different one, by name),
$ lpoptions -l
PageSize/Media Size: 3x5 A4 A5 A6 Env10 EnvC5 EnvDL EnvMonarch Executive FanFoldGermanLegal ISOB5 Legal *Letter Custom.WIDTHxHEIGHT
InputSlot/Media Source: *Auto Manual Tray1
MediaType/Media Type: *Stationery StationeryLightweight StationeryHeavyweight StationeryCover Envelope EnvelopeHeavyweight EnvelopeLightweight StationeryRecycled Labels StationeryBond
cupsPrintQuality/cupsPrintQuality: Draft *Normal High
ColorModel/Output Mode: *Gray
Duplex/Duplex: *None DuplexNoTumble DuplexTumble
OutputBin/OutputBin: *FaceDown
This listing shows the available configurable parameters, the possible values for them, and the current default (indicated with an asterisk).
You can also use the lpoptions(8)
command to change the default values. For
example, to change the default print quality to “High” (which you almost
certainly want to do, the lower quality settings are pretty poor, especially
for PDF documents), you would run,
$ lpoptions -o cupsPrintQuality=High
These options can also be specified at print time using the lp
command directly,
with the same -o option=value
syntax. So to print a document two-sided, with
the print settings above, you could run the following,1
$ lp -o Duplex=DuplexNoTumble document.pdf
I’ve noticed that it will sometimes take a shockingly long amount of time for the printer to actually print in Duplex, but it does eventually get there. I haven’t looked into why this occurs; if you happen to know, feel free to shoot me an email, I’d appreciate it.
Formatting text for printing
Linux comes with some utilities that are very helpful for pre-processing text for printing. For example, let’s say we wanted to print out the source code of a program that we’ve been working on. We could do this directly, using
$ lp program.c
But the resulting output won’t be super pretty. The margins will be extremely tight, it might not handle wrapping lines the way that you expect, etc.
The pr(1)
utility is designed for preprocessing text files being directed to a
printer. It has a huge number of features–I highly suggest that you check
out its manual. For our purposes here, let’s use it to add a slight margin,
and line numbers (always helpful when looking at code).2
$ pr -n -o 5 -f program.c | lp
This will add a header, page numbers, a margin of 5 spaces, as well as line
numbers, to the file. The -f
flag tells pr(1)
to send a formfeed, rather
than 5 blank lines, at the end of every page. The default behavior of adding
the blank lines will often cause the printer to spit out an extra blank page,
so the -f
flag helps avoid this issue.
However, you will still see ugliness in the output when a line needs to wrap,
as the wrapped part of the line will ignore the margin. Additionally, these
wrapped lines aren’t accounted for by pr(1)
when it is paginating, and so can
result in the pages output by the printer not lining up with the pages laid out
by pr(1)
. You can fix this manually, or use another helpful utility,
fmt(1)
3, to automatically break long lines prior to passing the code file
into pr(1)
.4
$ fmt -s -w70 program.c | pr -n -o 5 -F | lp
The -s
flag here tells fmt
to only split long lines. Otherwise, it will try
to merge short lines together as well, which is fine for prose but not at all
what we want for code. The -w
flag is used to specify the maximum width of
the output.
The reason for picking 70 for the line width here is because the standard width
of a sheet of paper is 80 characters. We are using a 5 character margin, and
the -n
flag in pr(1)
also uses 5 characters, so our content can be at most
70 characters long.
Conclusion
And that’s really all there is to it. Just a couple commands, and you’re off to
the races. I just started playing around with this myself, and I’m finding it
to be surprisingly convenient compared to the graphical interfaces. Plus, it’s
fun to finally have an excuse to play around with some of the text-file
processing commands like pr(1)
and fmt(1)
that I’d read about but never
needed to use.
I’ve been playing around the command-line interfaces to SANE too, for scanning documents. That one requires a little more manual work, so I may do a post or video about using them at some point too.
-
The nomenclature here is a bit confusing. DuplexNoTumble is the “normal” duplex mode, set up to turn the page along the long edge, like a book. DuplexNoTumble sets the pages up to turn along the short edge, like a top-bound notepad. ↩︎
-
You can run
pr(1)
without piping tolp(1)
to see on the terminal what the output would look like. This is pretty useful for saving paper and ink when tinkering with different output formats. ↩︎ -
fmt(1)
is rather simple and is not context aware in terms of the decisions it makes when breaking or merging lines. If you want something a little more powerful, you should check out thepar(1)
utility instead, which has many more features. ↩︎ -
The
pr(1)
command also has a-W
flag that can be used to specify the maximum width, howeverpr(1)
will truncate rather than reformat. ↩︎