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 printers
lpstat(8)– Used to view CUPS status information
lpoptions(8)– Used to configure the default options for a printer
lp(1)– Used to print files
First off, you’ll want to confirm that CUPS is running. You can do this using
lpstat command, with the
-t flag to make it show all available
$ 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
-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
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
To set the printer as your default, add this line to your shell’s profile/rc file,
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
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
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.
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,
-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
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
$ fmt -s -w70 program.c | pr -n -o 5 -F | lp
-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 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
-n flag in
pr(1) also uses 5 characters, so our content can be at most
70 characters long.
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
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 to
lp(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 the
par(1)utility instead, which has many more features. ↩︎
pr(1)command also has a
-Wflag that can be used to specify the maximum width, however
pr(1)will truncate rather than reformat. ↩︎