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,

  1. lpadmin(8) – Used to add and configure printers
  2. lpstat(8) – Used to view CUPS status information
  3. lpoptions(8) – Used to configure the default options for a printer
  4. lp(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

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.


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.

  1. 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. ↩︎

  2. 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. ↩︎

  3. 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. ↩︎

  4. The pr(1) command also has a -W flag that can be used to specify the maximum width, however pr(1) will truncate rather than reformat. ↩︎