> For the complete documentation index, see [llms.txt](https://tlcl.wenben.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tlcl.wenben.org/common-tasks-and-essential-tools/22-printing.md).

# 第22章：打印

在学习了这么多章节的文本操作之后，是时候把这些文本放在纸上了。本章中，我们将学习用来打印文件和控制打印机操作的命令行工具。我们不会学习如何配置打印，因为这是随着各个发行版不同而变化的，而且通常在安装时会自动设置好。注意，本章中我们需要一台配置良好的、可工作的打印机以供练习。

我们将讨论下列命令：

* `pr` 转换文本文件供打印
* `lpr` 打印文件
* `a2ps` 为 PostScript 打印机调整文件格式
* `lpstat` 显示打印机状态信息
* `lpq` 显示打印机序列状态
* `lprm` 取消打印任务

## 打印简史 <a href="#a-brief-history-of-printing" id="a-brief-history-of-printing"></a>

要完整地理解类 Unix 操作系统的打印特性，我们必须要学了解一点历史。类 Unix 系统上的打印要回溯到操作系统的开始阶段。那时候，打印机及其用法，和今天的很不一样。

### 昏暗时代的打印 <a href="#printing-in-the-dim-times" id="printing-in-the-dim-times"></a>

和计算机一样，打印机在前 PC 时代是庞大且昂贵和中心化的。典型的 1980 年代的计算机用户，工作在连接着远程计算机的终端前面。打印机则位于计算机附近，且在计算机操作者的监视之中。

当打印机还是昂贵且中心化的时候，常常是在 Unix 的早期，常见的情况是多个用户共享一台打印机。要识别打印任务属于哪个特定用户，在每个打印任务的开端会有一张标题页（*banner page*），显示用户的姓名。随后，计算机支持人员会将一天的打印任务装载在一个手推车上，分发给各个用户。

### 基于字符的打印机 <a href="#character-based-printers" id="character-based-printers"></a>

八十年代的打印机技术区别于今天的，有两个方面。首先，那个时代的打印机几乎全都是撞击式打印机（*impact printers*）。撞击式打印机使用的机械机制是对着纸张撞击一根绸带，以便在纸上形成字符印迹。那时流行的两个技术是菊花轮打印（*daisy-wheel*）和点阵打印（*dot-matrix*）。

其次，早期打印机更重要的特性是，打印机使用一套固化在设备中的固定字符集。例如，一台菊花轮打印机仅能打印铸在菊花轮花瓣中的字符。这使得打印机看起来更像高速打字机。如大多数打字机一样，它们使用等宽字体。意思是，每个字符的宽度是相等的。在页面的固定位置完成打印，页面上的可打印区域包含固定数目的字符。大多数打印机在水平方向每英寸中打印 10 个字符（CPI characters per inch），垂直方向每英寸打印 6 行（LPI line per inch）。使用该方案，一页美国信纸有 85 字符宽和 66 行高。考虑到每边的小边距，80 个字符被认为是打印行的最大宽度。这就是为何终端显示器（和我们的终端模拟器）正常情况下 80 字符宽的原因。使用等宽字体和 80 字符宽的终端，提供了一种所见即所得（*What You See Is What You Get WYSIWYG*）的打印效果。

数据以包含字符的简单的字节流被传送到打字机样式的打印机上供打印。例如，要打印一个 *a*，就传输 ASCII 码 97。另外，低数值的 ASCII 控制码提供的是移动打印机卷轴和纸张的意义，如使用回车符（carriage return）、换行符（line feed）、换页符（form feed）等等。使用控制符，可以达到某些受限制的字体效果，如粗体字，是通过打印一个字符后退格，再打印一次该字符，在纸上就得到了更深的印迹。如果使用 `nroff` 渲染一个手册页，用 `cat -A` 检验输出，我们确实可以见证此效果。

```bash
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | nroff -man | cat -A | head
LS(1)                       User Commands                      LS(1)
$
$
$
N^HNA^HAM^HME^HE$
       ls - list directory contents$
$
S^HSY^HYN^HNO^HOP^HPS^HSI^HIS^HS$
       l^Hls^Hs [_^HO_^HP_^HT_^HI_^HO_^HN]... [_^HF_^HI_^HL_^HE]...$
```

`^H`（Ctrl-h）字符是用来制造粗体效果的退格键。同样的，我们可以用退格键和下划线组合序列来制造下划线。

### 图形打印机 <a href="#graphical-printers" id="graphical-printers"></a>

图形用户界面的发展导致打印机技术的巨大改变。当计算机转换到更多的基于图形的显示时，打印也从基于字符转换到图形技术。低成本激光打印机的出现促进了这一点，该打印机不是打印固定字符，而是可以在页面的可打印区域中的任何位置打印小点。这使得打印比例字体（和那些排字工所用的一样）甚至图形和高质量图表成为可能。

然而，从基于字符的方案转移到图形方案展现出一个可怕的技术挑战。因为：使用基于字符的打印机填充一页纸所需的字节数是可以通过下列算式得出的（假设每页 60 行、每行 80 字）：

60 x 80 = 4,800 字节

比较一下，一个每英寸 300 点（DPI dot per inch）的激光打印机（假设每页可打印区域是 8 x 10 英寸）需要的字节数是：

(8 x 300) x (10 x 300) / 8 = 900,000 字节

很多缓慢的网络直接不能处理这近 1 兆打印到激光打印机上一张纸的数据，所以很明显，需要一个更聪明的发明。

这发明的结果就是页面描述语言（PDL *Page description language*）。页面描述语言是一种描述页面内容的编程语言。基本地，它会说「到这个位置，用 10 点的 Helvetica 画一个字符 'a'，到这个位置……」直到页面上的所有都被描述清楚。第一个重要的 PDL 是来自 Adobe 系统公司的 *PostScript*，直到今天还在广泛应用。PostScript 语言是为排版和其它类型的图形图像量身定制的完整的编程语言。其内建支持 35 种标准高品质字体，还有在运行时接受额外字体定义的能力。最初，对 PostScript 的支持是内建在打印机自身中的。这解决了传输数据的问题。典型的 PostScript 程序对比简单字节流的基于字符的打印机，显得非常冗长，而其所需表示一张完整页面的字节数则远小于彼。

一台 PostScript 打印机以一个 PostScript 程序为输入。打印机包含其自身的处理器和内存（通常使打印机成为比它附带的计算机更强大的计算机），执行一个被成为 PostScript 解释器的特定程序，因此形成可以被传输到纸上的比特样式（点）。将某些东西渲染成大位模式（位图 *bitmap*）的过程的通用名称是光栅图像处理器（RIP *raster image processor*）。

很多年过去了，计算机和网络都变得更快速了。这就允许 RIP 从打印机转移到主机，作为回报，高质量的打印机可以更廉价了。

今天的很多打印机还能接受基于字符的文本流，但是很多低成本的打印机则不能。它们依赖于主机 RIP 提供比特流供点打印。还有一些 PostScript 打印机。

## 用 Linux 打印 <a href="#printing-with-linux" id="printing-with-linux"></a>

现代 Linux 系统采用两个软件套装执行并管理打印。第一个是常用 Unix 打印系统（CUPS）提供打印驱动和打印任务管理，第二个 Ghostscript，一个 PostScript 解释器，和 RIP 一样。

CUPS 通过创建并维护打印序列（*print queues*）来管理打印机。如我们在早期历史的课程中所讨论的，Unix 打印最初设计为管理中心化的打印机，共享给多个用户。因为打印机天然就比输送给它的计算机慢，打印系统需要有途径管理多个打印任务，保持事务井井有条。CUPS 还有能识别不同数据类型的能力（在合理范围内）和可以转换文件到可打印形式的能力。

## 为打印准备文件 <a href="#preparing-files-for-printing" id="preparing-files-for-printing"></a>

作为命令行用户，我们最感兴趣的就是打印文本，尽管它当然还可能打印其它数据格式。

### pr - 为打印转换文本文件 <a href="#pr-convert-text-files-for-printing" id="pr-convert-text-files-for-printing"></a>

上一章中我们接触到了一点儿 `pr`。现在将要研究其用于与打印相关联的众多选项。在打印的历史中，我们看到了基于字符打印机是使用等宽字体的，导致了固定的每行字符数和固定的每页行数。`pr` 用来调整文本以适应特定纸型，同时可选页眉和边距。表 22-1 总结了最常用的选项。

表 22-1：常用 `pr` 选项

| 选项              | 描述                                                   |
| --------------- | ---------------------------------------------------- |
| `+first[:last]` | 从 `first` 页开始输出页面序列，以可选的 `last` 页结束。                 |
| `-columns`      | 将页面内容组织为 `comlumn` 数目的栏位。                            |
| `-a`            | 默认情况下多栏输出是垂直方向列出。通过加 `-a` （横跨）选项，则水平分栏。              |
| `-d`            | 双倍空格输出。                                              |
| `-D "format"`   | 用 `format` 格式显示页眉上的日期。可以查看 `date` 命令的手册页以获取格式字符串的描述。 |
| `-f`            | 使用换页符而非回车符来分隔页面。                                     |
| `-h "header"`   | 在页眉中部，使用 `"header"` 而非正在处理的文件名。                      |
| `-l length`     | 将页面长度设置为 `length`。默认值为 66（美国信纸每英寸 6 行）。              |
| `-n`            | 编行号。                                                 |
| `-o offset`     | 创建一个宽为 `offset` 字符的左边距。                              |
| `-w width`      | 设置页面宽度为 `width`。默认值为 72。                             |

`pr` 常被用在管道命令中作为一种过滤器。下面这个例子中，我们制作一个 `/usr/bin` 目录的清单，用 `pr` 格式化为三栏的分页输出：

```bash
[me@linuxbox ~]$ ls /usr/bin | pr -3 -w 65 | head

2016-02-18 14:00                                          Page 1
[                 apturl             bsd-write
411toppm          ar                 bsh
a2p               arecord            btcflash
a2ps              arecordmidi        bug-buddy
a2ps-lpr-wrapper  ark                buildhash
```

## 将打印任务送达打印机 <a href="#sending-a-print-job-to-a-printer" id="sending-a-print-job-to-a-printer"></a>

CUPS 打印套件支持两种模式的打印，都是历史上用在类 Unix 系统上的。第一种叫伯克利或者 LPD（用在 Unix 伯克利软件发行版），使用 `lpr` 程序，第二种叫 SysV（来自 System V 版本的 Unix），使用 `lp` 程序。两个程序粗略看来就是一回事。选择哪种，取决于个人喜好。

### lpr - 打印文件（伯克利风格） <a href="#lpr-print-files-berkeley-style" id="lpr-print-files-berkeley-style"></a>

`lpr` 程序可以用来将文件发送到打印机。也可以用在管道命令中，接收标准输入。例如，要打印之前的多列目录列表，可以如下操作：

```bash
[me@linuxbox ~]$ ls /usr/bin | pr -3 | lpr
```

报告会被发送到系统默认的打印机。要发送到另一台打印机，需要用 `-P` 选项：

```bash
lpr -P printer_name
```

这里的 `printer_name` 是所需打印机的名称。要查看系统所能识别的打印机清单，用如下命令：

```bash
[me@linuxbox ~]$ lpstat -a
```

> **提示：**&#x8BB8;多 Linux 发行版允许你定义一台「打印机」以输出 PDF 文件，而不是打印到物理打印机。这对试验打印命令非常方便。检查你的打印机配置程序，查看其是否支持这项配置。在某些发行版中，你可能需要安装额外的软件包（如 `cups-pdf`）以激活该功能。

表 22-2 描述了 `lpr` 的常见选项。

表 22-2：常见 `lpr` 选项

| 选项           | 描述                                                    |
| ------------ | ----------------------------------------------------- |
| `# number`   | 设置打印份数。                                               |
| `-p`         | 在每页打印带阴影的页眉，包含日期、时间、任务名称和页码。这被称为「美化打印」选项，可以用在打印文本文件时。 |
| `-P printer` | 指定用于输出的打印机名称。如果没有指定，则使用系统默认打印机。                       |
| `-r`         | 在打印后删除文件。该选项对在制作临时打印输出文件的程序很有用。                       |

### lp - 打印文件（System V 风格） <a href="#lp-print-files-system-v-style" id="lp-print-files-system-v-style"></a>

和 `lpr` 一样，`lp` 可以接收文件或标准输入以打印。区别于 `lpr` 的是其支持一套不同的（有些更复杂的）选项。表 22-3 描述了常见选项。

表 22-3：常见 `lp` 选项

| 选项                                                                                                                                                    | 描述                                                         |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `-d printer`                                                                                                                                          | 设置目标（打印机）为 `printer`。如果没有指定 `d` 选项，则使用系统默认打印机。             |
| `-n number`                                                                                                                                           | 设置打印份数。                                                    |
| `-o landscape`                                                                                                                                        | 设置横向输出。                                                    |
| `-o fitplot`                                                                                                                                          | 缩放文件以适应页面。当打印如 JPEG 图像文件时有用。                               |
| `-o scaling=number`                                                                                                                                   | 缩放文件到 `number`。值为 100 则布满页面。少于 100 则减小，大于 100 会导致文件打印在多页上。 |
| `-o cpi=number`                                                                                                                                       | 设置每英寸字符数。默认是 10。                                           |
| `-o lpi=number`                                                                                                                                       | 设置每英寸行数。默认是 6。                                             |
| <p><code>-o page-bottom=points</code><br><code>-o page-left=points</code><br><code>-o page-right=points</code><br><code>-o page-top=points</code></p> | 设置页面边距。值的单位是点（*points*），一种印刷测量单位。每英寸为 72 点。                |
| `-p pages`                                                                                                                                            | 指定页面列表。`pages` 可以是逗号分隔的列表，也可以是一个序列，例如，`1,3,5,7-10`。        |

再来制作一次目录清单，这次使用 12 CPI 和 8 LPI，同时有半英寸的左边距。注意，考虑到新的页面尺寸，我们必须调整 `pr` 选项。

```bash
[me@linuxbox ~]$ ls /usr/bin | pr -4 -w 90 -l 88 | lp -o page-left=36 -o cpi=12 -o lpi=8
```

这个管道命令制作了一个四栏列表，使用了比默认值更小的字体。增加每英寸字符数允许我们适应页面上更多的栏。

### 另一个选项： a2ps <a href="#another-option-a2ps" id="another-option-a2ps"></a>

`a2ps` 程序（在大多数软件仓库中可用）很有意思。我们可以从它的名称中推测其为一个转换格式的程序，不过还不止于此。它得名于 "ASCII to PostScript"，用来预备文本文件以供 PostScript 打印机打印。很多年过去了，程序的能力也在成长，现在它的名字意味着 "Anything to PostScript"。看它的名字可能是个格式转换程序，但是实际上它是个打印程序。它会将其默认输出到系统默认打印机而非标准输出。程序的默认行为是个「美观打印机」，意味着它改进了输出的显示。我们使用程序来创建一个 PostScript 文件到桌面上。

```bash
[me@linuxbox ~]$ ls /usr/bin | pr -3 -t | a2ps -o ~/Desktop/ls.ps -L 66
[stdin (plain): 11 pages on 6 sheets]
[Total: 11 pages on 6 sheets] saved into the file '/home/me/Desktop/ls.ps'
```

我们用 `pr` 筛选文本流，用 `-t` 选项（忽略页眉页脚），随后使用 `a2ps`，指定一个输出文件（`-o` 选项）、每页 66 行（`-L` 选项），以匹配 `pr` 的输出分页。要是我们用合适的文件查看器打开结果文件，会看到如下图所示：

![查看 a2ps 输出](/files/-Lp7Yat9LN5JudOgv16y)

可以看到，默认的输出布局是「拼页」格式。使得每张纸上都打印两页内容。`a2ps` 同时还应用了漂亮的页眉页脚。

`a2ps` 有很多选项。表 22-4 提供了一个概览。

表 22-4：`a2ps` 选项

| 选项                        | 描述                                                                           |
| ------------------------- | ---------------------------------------------------------------------------- |
| `--center-title=text`     | 设置页面居中标题为 `text`。                                                            |
| `--columns=number`        | 将页面整理为 `number` 列。默认为 2。                                                     |
| `--footer=text`           | 设置页脚文字为 `text`。                                                              |
| `--guess`                 | 报告作为参数给出的文件类型。 由于 `a2ps` 尝试转换和格式化所有类型的数据，因此该选项对于预测给定特定文件时 `a2ps` 将执行的操作非常有用。 |
| `--left-footer=text`      | 设置左页面的页脚为 `text`。                                                            |
| `--left-title=text`       | 设置左页面的页眉为 `text`。                                                            |
| `--line-numbers=interval` | 设置每隔 `interval` 行编行号。                                                        |
| `--list=defaults`         | 显示默认设置。                                                                      |
| `--pages=range`           | 打印序列中的页面。                                                                    |
| `--right-footer=text`     | 设置右页面的页脚为 `text`。                                                            |
| `--right-title=text`      | 设置右页面的页眉为 `text`。                                                            |
| `--rows=number`           | 将页面整理为 `number` 行。默认为 1。                                                     |
| `-B`                      | 不显示页眉。                                                                       |
| `-b text`                 | 设置页眉为 `text`。                                                                |
| `-f size`                 | 使用 `size` 大小的字体。                                                             |
| `-l number`               | 设置每行字数为 `number`。这和 `-L` 选项（下面一条）可以用来和其它程序，如 `pr` 一起制作文件分页，正确地适应页面。          |
| `-L number`               | 设置每页行数为 `number`。                                                            |
| `-M name`                 | 使用媒体 `name`。如 `A4`。                                                          |
| `-n number`               | 设置每页输出的份数为 `number`。                                                         |
| `-o file`                 | 将输出发送到 `file`。如果 `file` 被指定为 `-`，则使用标准输出。                                    |
| `-P printer`              | 使用 `printer` 打印机。如果没有指定，则使用系统默认打印机。                                          |
| `-R`                      | 纵向输出。                                                                        |
| `-r`                      | 横向输出。                                                                        |
| `-T number`               | 设置制表符的位置为 `number` 字符。                                                       |
| `-u text`                 | 设置页面水印为 `text`。                                                              |

这仅仅是个概览。`a2ps` 还有更多的选项。

> **注意：**&#x8FD8;有一个输出格式程序，用来将文本转换为 PostScript。名为 `enscript`，它可以执行同样的格式和打印技巧，但是和 `a2ps` 不同的是，它仅仅接收文本输入。

## 监视和控制打印任务 <a href="#monitoring-and-controlling-print-jobs" id="monitoring-and-controlling-print-jobs"></a>

因为 Unix 打印系统是涉及为处理多用户的多任务的，所以 CUPS 也是如此。每个打印机有个打印序列（*print queue*），任务会停在那里，直到它们被假脱机到打印机。CUPS 提供了几个命令行程序用以管理打印机状态和打印序列。和 `lpr` 及 `lp` 程序一样，这些管理程序是与伯克利和 System V 打印系统相应的。

### lpstat - 显示打印系统状态 <a href="#lpstat-display-print-system-status" id="lpstat-display-print-system-status"></a>

`lpstat` 程序对确定系统上的打印机的名称和可用性很有用。例如，如果我们的系统有一台物理打印机（名为 printer）和一台 PDF 虚拟打印机（名为 PDF），我们可以这样去检测它们的状态：

```bash
[me@linuxbox ~]$ lpstat -a
PDF accepting requests since Mon 08 Dec 2017 03:05:59 PM EST
printer accepting requests since Tue 24 Feb 2018 08:43:22 AM EST
```

更进一步，可以确定打印系统配置更细节的描述，如下：

```bash
[me@linuxbox ~]$ lpstat -s
system default destination: printer
device for PDF: cups-pdf:/
device for printer: ipp://print-server:631/printers/printer
```

本例中，我们看到 "printer" 是系统默认打印机，还有一台使用因特网打印协议（Internet Printing Protocol ipp\://）挂载在系统的名为 "print-server" 的网络打印机。

表 22-5 列出了常用选项。

表 22-5：常用 `lpstat` 选项

| 选项                | 描述                                                                         |
| ----------------- | -------------------------------------------------------------------------- |
| `-a [printer...]` | 显示 `printer` 打印机的打印机序列状态。注意这是打印机序列接收任务的能力的状态，不是物理打印机的状态。若不指定打印机，则列出所有打印序列。 |
| `-d`              | 显示系统默认打印机的名称。                                                              |
| `-p [printer...]` | 显示指定打印机 `printer` 的状态。如果没有指定打印机，则显示全部。                                     |
| `-r`              | 显示打印服务器的状态。                                                                |
| `-s`              | 显示状态概览。                                                                    |
| `-t`              | 显示一份完整的状态报告。                                                               |

### lpq - 显示打印机队列状态 <a href="#lpq-display-printer-queue-status" id="lpq-display-printer-queue-status"></a>

要查看打印机序列的状态，就要用到 `lpq` 程序。这允许我们查看序列的状态及其打印任务。这里是一个对名为 "printer" 的系统默认打印机的空序列的示例。

```bash
[me@linuxbox ~]$ lpq
printer is ready
no entries
```

如果我们没有指定打印机（用 `-P` 选项），则显示系统默认打印机。如果我们发送了一个任务到打印机，然后再看序列，可以看到已经被列出了。

```bash
[me@linuxbox ~]$ ls *.txt | pr -3 | lp
request id is printer-603 (1 file(s))
[me@linuxbox ~]$ lpq
printer is ready and printing
Rank    Owner   Job     File(s)          Total Size
active  me      603     (stdin)          1024 bytes
```

### lprm / cancel - 取消打印任务 <a href="#lprm-cancel-cancel-print-jobs" id="lprm-cancel-cancel-print-jobs"></a>

CUPS 提供两个程序用来结束打印任务并将其从打印序列中移除。一个是伯克利风格的（`lprm`），一个是 System V（`cancel`）。两者之间区别在于所支持的选项略有不同，不过基本上是完成同一件事。使用早先的打印任务作为示例，我们可以按下例所示，停止任务，移除序列：

```bash
[me@linuxbox ~]$ cancel 603
[me@linuxbox ~]$ lpq
printer is ready
no entries
```

每个命令对于移除某个特定用户的所有任务、或者特定打印的所有任务、或者多个任务编号的任务，都各有其选项。它们各自的手册页中有详细的说明。

## 总结 <a href="#summing-up" id="summing-up"></a>

本章中，我们看到了过去的打印机如何影响类 Unix 机器上的打印系统的设计，以及命令行上有多少控制是可用的，不仅仅是控制打印任务的计划和执行，而且还有各种输出选项。

## 扩展阅读 <a href="#further-reading" id="further-reading"></a>

* 一篇关于 PostScript 页面描述语言的好文章：<http://en.wikipedia.org/wiki/PostScript>
* 普通 Unix 打印系统（CUPS）：<http://en.wikipedia.org/wiki/Common_Unix_Printing_System>
* 伯克利和 System V 打印系统
  * <http://en.wikipedia.org/wiki/Berkeley_printing_system>
  * <http://en.wikipedia.org/wiki/System_V_printing_system>
