---
title: 第 10 章 打印
part: 部分 II. 常见的任务
prev: books/handbook/kernelconfig
next: books/handbook/linuxemu
showBookMenu: true
weight: 13
path: "/books/handbook/"
---

[[printing]]
= 打印
:doctype: book
:toc: macro
:toclevels: 1
:icons: font
:sectnums:
:sectnumlevels: 6
:sectnumoffset: 10
:partnums:
:source-highlighter: rouge
:experimental:
:images-path: books/handbook/printing/

ifdef::env-beastie[]
ifdef::backend-html5[]
:imagesdir: ../../../../images/{images-path}
endif::[]
ifndef::book[]
include::shared/authors.adoc[]
include::shared/mirrors.adoc[]
include::shared/releases.adoc[]
include::shared/attributes/attributes-{{% lang %}}.adoc[]
include::shared/{{% lang %}}/teams.adoc[]
include::shared/{{% lang %}}/mailing-lists.adoc[]
include::shared/{{% lang %}}/urls.adoc[]
toc::[]
endif::[]
ifdef::backend-pdf,backend-epub3[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]
endif::[]

ifndef::env-beastie[]
toc::[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]

[[printing-synopsis]]
== 概述

FreeBSD 可以支持众多种类的打印机， 从最古老的针式打印机到最新的激光打印机以及它们之间所有类型的打印机， 令您运行的应用程序产生高质量的打印输出。

FreeBSD 也可以配置成网络打印服务器。 它可以从包括 FreeBSD、 Windows(R) 及 Mac OS(R) 在内的多种其他计算机上接收打印任务。 FreeBSD 将保证打印任务之间不会相互干扰并一次性完成， 而且能够对机器或用户提交打印任务的情况进行统计并找到其中用量最多的人， 以及生成用于标识打印任务属于哪位用户的 "标签" 页等等。

在读完这章后，您将知道：

* 怎样配置 FreeBSD 后台打印。
* 怎样安装打印过滤器来对特殊的打印任务做特殊的处理， 包括把传来的文档转换成打印机能理解的格式。
* 怎样在打印输出上开启报头或者横幅页功能。
* 怎样打印到连接在其他计算机上的打印机。
* 怎样打印到直接连接在网络上的打印机。
* 怎样控制打印机的限制， 包括限制打印任务的大小和阻止某些用户打印。
* 怎样记录打印机统计表和使用情况。
* 怎样解决打印故障。

在读这章之前， 您应该：

* 知道怎样配置并安装新内核 (crossref:kernelconfig[kernelconfig,配置FreeBSD的内核])。

[[printing-intro-spooler]]
== 介绍

为了在 FreeBSD 中使用打印机， 需要首先配置好伯克利行式打印机后台打印系统即 LPD。 它是 FreeBSD 的标准打印控制系统。 这章介绍 LPD 后台打印系统， 在接下来将简称为 LPD， 并且将指导您完成其配置。

如果您已经熟悉了 LPD 或者其他后台打印系统， 则可以跳到 <<printing-intro-setup,设置后台打印系统>> 这部分。

LPD 完全控制一台计算机上的打印机。 它负责许多的事情：

* 它控制本地和连接在网络上其他计算机上打印机的访问。
* 它允许用户提交要打印的文件; 这些通常被认为是__任务__。
* 它为每个打印机维护一个 __队列__ 来防止多个用户在同一时刻访问一台打印机。
* 它可以打印__报头__(也叫做__banner__或者__ burst__页使用户可以轻松的从一堆打印输出中找到它们打印的任务。
* 它来设置连接在串口上的打印机的通讯参数。
* 它能通过网络将任务发送到另外一台计算机的 LPD后台打印队列中。
* 它可以根据不同种类的打印机语言和打印机的性能运行特殊的过滤器来格式化任务。
* 它记录打印机的使用情况。

通过配置文件 ([.filename]#/etc/printcap#)和提供的特殊过滤程序， 您可以使LPD 系统在众多种类的打印机硬件上完成上面全部的或者一些子集的功能。

[[printing-intro-why]]
=== 为什么要用后台打印

如果您是系统唯一的用户， 您可能会奇怪为什么要在您不需要访问控制， 报头页或者打印机使用统计时为后台打印费心。 它可以设置成允许直接访问打印机， 但您还是应该使用后台打印， 因为：

* LPD在后台打印任务； 您不用被迫等待数据被完全副本到打印机的时间。
* LPD可以可以方便的通过过滤器给任务加上日期/ 时间的页眉或者把一种特殊的文件格式 (比如TeX DVI 文件) 转换成一种打印机可以理解的格式。 您不必去手动做这些步骤。
* 许多提供打印功能的免费和商业程序想要和您计算机上的的后台打印系统通讯。 通过设置后台打印系统， 您将更轻松的支持其他以后要添加的或者现有的软件。

[[printing-intro-setup]]
== 基本设置

[WARNING]
====

从 FreeBSD 8.0 起， 串口对应的设备名由 [.filename]#/dev/ttydN# 变为 [.filename]#/dev/ttyuN#。 FreeBSD 7.X 用户应将这篇文档的示例中的设备名改为原先的样子。
====

要想在 LPD后台打印系统上使用打印机， 您需要设置打印机硬件和 LPD软件。 这个 文档描述了这两级设置：

* 参见<<printing-simple,简单打印机 设置>>来了解怎样连接一个打印机， 告诉 LPD怎样与 它通讯， 并且打印纯文本到 打印机。
* 参见 <<printing-advanced,高级打印机设置>> 来了解怎样打印多种 特殊格式的文件， 怎样打印报头页， 怎样通过网络 打印， 怎样控制打印机的访问权限， 并且学会为打印 作业记帐统计。

[[printing-simple]]
=== 简单打印机设置

这部分讲解怎样配置打印机硬件和 LPD使之与打印机配合。 讲解的基础知识有：

* <<printing-hardware,硬件 设置>>部分将讲解怎样把一台打印机连接到 您计算机的一个端口上。
* <<printing-software,软件 设置>>部分将讲解怎样配置 LPD后台打印的配置 文件 ([.filename]#/etc/printcap#)。

如果您正在设置一台通过网络协议 接收数据来打印而不是通过串口或者并口的打印机， 参见 <<printing-advanced-network-net-if,使用网络数据流界面的打印机>>。

尽管这部分叫"简单打印机 设置"， 但还是相当复杂的。 使打印机 配合 LPD 后台打印系统在计算机上正常运转是最难的 部分。 一旦您的打印机可以正常工作后，那些高级选项， 比如报文页和记帐， 是相当简单的。

[[printing-hardware]]
==== 硬件设置

这部分讲述了打印机连接到计算机的多种 途径。 主要讨论了多种接口和 连接线， 还有允许 FreeBSD 与打印机通讯所需的 内核配置。

如果您已经连接好了您的打印机而且已经 用它在另外一个操作系统下成功的打印， 您 或许可以跳到这个部分<<printing-software,软件设置>>。

[[printing-ports]]
===== 端口和连接电缆

现在所出售的在 PC 上使用的打印机通常至少有 以下三种接口中的一个：

* _串口_， 也叫 RS-232 或者 COM 口， 使用您计算机上的串口来发送数据到打印机。 串口在计算机上已经非常普遍， 而且电缆也非常容易买到且容易制作。 串口有时需要特殊的电缆， 而且可能需要您去配置稍微有点儿复杂的通讯选项。 大多数 PC 的串口的最高传输速度只有 115200 bps， 这使得打印很大的图像需要的时间很长。
* _并口_ 使用计算机上的并口来发送数据到打印机。 并口在计算机上也已经非常普遍， 而且速度高于 RS-232 串口。 电缆非常容易买到， 但很难手工制作。 并口通常没有通讯选项， 这使得配置它相当简单。
+ 
并口按打印机上的接头来命名也叫做 "Centronics"接口。
* USB 接口， 即通用串行总线， 可以达到比并口和串口高很多的速度。 其电缆既简单又便宜。 USB 用来打印比串口和并口更有优势， 但 UNIX(R) 系统不能很好的支持它。 避免这个问题的方法就是购买一台 像大多数打印机一样的既有 USB 接口又有并口的 打印机。

一般来说并口只提供单向通讯 (计算机到打印机)， 而串口和 USB 则可以提供双向通讯。 新的并口 (EPP 和 ECP) 及打印机在使用了 IEEE-1284 标准的电缆之后， 可以在 FreeBSD 下双向通讯。

与打印机通过并口双向通讯通常由这两种方法中的一种来完成。 第一个方法是使用为 FreeBSD 编写的可以通过打印机使用的语言与打印机通讯的驱动程序。 这通常用在喷墨打印机上， 且可以用来报告剩余墨水多少和其他状态信息。 第二种方法使用在支持 PostScript(R) 的打印机上。

PostScript(R) 任务事实上由程序发送给打印机； 但它并不进行打印而是直接将结果返回给计算机。 PostScript(R) 也采取双向通讯来将打印中的问题报告给计算机， 比如 PostScript(R) 程序中的错误或者打印机卡纸。 这些信息对于用户来说也许是非常有价值的。 此外， 最好的在支持 PostScript(R) 的打印机上记帐的方法需要双向通讯： 询问打印机打印总页数 (打印机从出厂一共打印过多少页)， 然后发送用户的任务， 之后再次查询总打印页数。 将打印前后得到的两个值相减就可以得到该用户要付多少纸钱。

[[printing-parallel]]
===== 并口

用并口连接打印机需要用 Centronics 电缆把打印机与计算机连接起来。 具体说明指导在打印机， 计算机的说明书上应该有， 或者干脆两个上面都有。

记住您用的计算机上的哪个并口。 第一个并口在 FreeBSD 上叫 [.filename]#/dev/ppc0#； 第二个叫 [.filename]#/dev/ppc1#， 依此类推。打印机设备也用同样的方法命名： [.filename]#/dev/lpt0# 是接在第一个并口上的打印机， 依此类推。

[[printing-serial]]
===== 串口

用串口连接打印机需要用 合适的串口电缆把打印机与计算机连接起来。 具体 说明指导应该在打印机， 计算机的说明书上有， 或者 同样干脆两个上面都有。

如果您不确定什么样儿的电缆才是 " 合适的串口 电缆 " ， 您可以尝试以下几种不同的 电缆：

* _调制解调器_ 电缆每一端的 每一根引脚都直接连接到另一端 相应的引脚 上。 这种电缆也叫做 "DTE-to-DCE" 电缆。
* __非调制解调器__电缆上每一端的有些引脚 是与另一端相应引脚直接连接的， 而有一些则是交叉连接的 (比如， 发送数据引脚连接到 接收数据引脚 )， 还有一些引脚直接在电缆连接头儿内 短接。 这种电缆也叫做 "DTE-to-DTE" 电缆。
* 一些特殊的打印机需要的__串口打印机__ 电缆， 是一种和非调制解调器电缆类似的电缆， 只是一些信号还是送到了另一端， 而 不是直接在连接头儿内短路。

当然， 您还得为打印机设置通讯参数。 一般是通过打印机面板上的按钮或者 DIP 开关进行设置。 在计算机和打印机上都选择它们所支持的最高 `波特` (每秒多少比特， 有时也叫 _波特率_) 的传输速率。 选择7或者8个数据位； 选择不校验， 偶校验或者奇校验； 选择1个或2个停止位。 还要选择流量控制协议： 无， XON/XOFF (也叫做 "in-band" 或 "软件") 流量控制。 记住您的软件配置中的参数也要设成上面的数值。

[[printing-software]]
==== 软件设置

这部分描述了要使用 FreeBSD 系统中的 LPD 后台打印系统进行打印所需的软件设置。 

包括这几个步骤：

[.procedure]
====
. 在需要的时候配置内核来允许您连接 打印机的端口； <<printing-kernel,配置内核>> 部分会告诉您 需要做什么。
. 如果您使用并口， 则需要设置一下 并口的通讯模式; <<printing-parallel-port-mode,设置 并口通讯模式>> 部分会告诉您具体的 细节。
. 测试操作系统是否能够发送数据到打印机。 <<printing-testing,检测打印机 联机状况>> 部分会告诉您要怎样 做。
. 为 LPD 设置与打印机匹配的参数则 通过修改 [.filename]#/etc/printcap# 这个文件来完成。 这章后面 的部分将讲解如何来完成设置。
====

[[printing-kernel]]
===== 配置内核

操作系统的内核为了使某些特殊设备工作需要重新 编译。 打印机所用的串口、 并口就属于那些特殊设备。 因此， 可能需要 添加对串口或并口的支持， 如果内核并没有配置它们的话。

要想知道您现在使用的内核是否支持串口， 输入：

[source,shell]
....
# grep sioN /var/run/dmesg.boot
....

其中 _N_ 是串口的 编号， 从0开始。 如果您看到 类似下面的输出：

[source,shell]
....
sio2 at port 0x3e8-0x3ef irq 5 on isa
sio2: type 16550A
....

则说明您现在使用的内核支持串口。

要想知道您现在使用的内核是否支持并口， 输入：

[source,shell]
....
# grep ppcN /var/run/dmesg.boot
....

其中 _N_ 是并口的 编号， 同样从0开始。 如果得到类似 下面的输出：

[source,shell]
....
ppc0: <Parallel port> at port 0x378-0x37f irq 7 on isa0
ppc0: SMC-like chipset (ECP/EPP/PS2/NIBBLE) in COMPATIBLE mode
ppc0: FIFO with 16/16/8 bytes threshold
....

那么您现在使用的内核支持并口。

您可能必须为了使操作系统支持您打印机需要的串口或 并口而 重新配置内核。

要增加对串口的支持， 参见 内核配置这部分。 要增加对并口的支持， 除了参见 上面提到的那部分之外， _还要_ 参见下面的 部分。

[[printing-parallel-port-mode]]
==== 设置并口的通讯模式

在使用并口时， 您可以选择 让 FreeBSD 用中断方式还是轮询方式来 与打印机通讯。 在 FreeBSD 上， 通用的打印机驱动 (man:lpt[4]) 使用 man:ppbus[4] 系统， 它利用 man:ppc[4] 驱动来控制端口芯片。

* _中断_ 方式是 GENERIC 核心的默认方式。 在这种方式下， 操作系统占用一条中断请求线来检测打印机是否已经做好接收数据的准备。
* _轮询_ 方式是操作系统反复不断的询问打印机是否做好接收数据的准备。 当它返回就绪时， 核心开始发送下面要发送的数据。

中断方式速度通常会快一些， 但却占用了一条宝贵的中断请求线。 一些新出的 HP 打印机 不能正常的工作在中断模式下， 是由于一些定时问题 (还没正确的理解) 造成的。 这些打印机需要使用轮询方式。 您应该使用 任何一种方式， 只要它能正常工作就行。 一些打印机虽然在两种模式下都可以 工作， 但在中断模式下会慢的要命。

您可以用以下两种方法设定通讯模式： 通过 配置内核或者使用 man:lptcontrol[8] 这个程序。

_要通过配置内核的方法设置 通讯模式：_

[.procedure]
====

. 修改内核配置文件。 找到 一个叫 `ppc0` 的记录。 如果您想要设置的是 第二个并口， 那么用 `ppc1` 代替。 使用第三个并口的时候用 `ppc2` 代替， 依此类推。
+
** 如果您希望使用中断驱动模式， 则应编辑下面的配置：
+
[.programlisting]
....
hint.ppc.0.irq="N"
....
+ 
它在 [.filename]#/boot/device.hints# 这个文件中， 其中 _N_ 用正确的中断 编号代替。 同时， 核心配置文件也必须 包括 man:ppc[4] 的驱动：
+
[source,shell]
....
device ppc
....
+
** 如果您想要使用轮询方式， 只需要把 [.filename]#/boot/device.hints# 这个文件中的下面这行删除掉：
+
[.programlisting]
....
hint.ppc.0.irq="N"
....
+ 
在 FreeBSD 下， 有时上面的方法并不能使并口工作在轮询方式。 大多数情况是由于 man:acpi[4] 驱动造成的， 它可以自动侦测到设备并将其挂载到系统上， 但也因此， 它控制着打印机端口的访问模式. 您需要检查 man:acpi[4] 的配置来解决这个问题。
+
. 保存文件。 然后配置， 建立， 并安装刚配置的内核， 最后重新启动。 参见 crossref:kernelconfig[kernelconfig,内核配置] 这章来获得更多细节。
====

_使用_ man:lptcontrol[8] _设置通讯模式_：

[.procedure]
====

. 输入：
+
[source,shell]
....
# lptcontrol -i -d /dev/lptN
....
+ 
将 `lptN` 设置成中断方式。
. 输入：
+
[source,shell]
....
# lptcontrol -p -d /dev/lptN
....
+ 
将 `lptN` 设置成轮询方式。
====

您可以把这些命令加入到 [.filename]#/etc/rc.local# 这个文件中， 这样每次启动系统 时都会设置成您想要的方式。 参见 man:lptcontrol[8] 来获得 更多信息。

[[printing-testing]]
==== 检测打印机的通讯

在设置后台打印系统之前， 您应该确保您的计算机可以把数据 发送到打印机上。 分别独立调试打印机的通讯和后台打印系统会更简单。

我们为了测试打印机，将发送一些文本给它。 一个叫 man:lptest[1] 的程序能胜任这项工作， 它可以让打印机立即打印出程序发给它的 字符： 它在每行打出 可以打印的 96 个 ASCII 字符。

当我们使用的是一台 PostScript(R) ( 或者以其他语言为基础的 ) 打印机， 那么 需要更仔细的检测。 一段小小的 PostScript(R) 程序足以完成检测的任务， 比如下面这段程序：

[.programlisting]
....
%!PS
100 100 moveto 300 300 lineto stroke
310 310 moveto /Helvetica findfont 12 scalefont setfont
(Is this thing working?) show
showpage
....

可以把上面这段 PostScript(R) 代码写进一个文件里， 并且像下面部分的例子里那样 使用。

[NOTE]
====
上面的小程序是针对 PostScript(R) 而不是惠普的 PCL 写的。 由于 PCL 拥有许多其他打印机没有的强大功能， 比如它支持在打印纯文本的同时夹带特殊的命令， 而 PostScript(R) 则不能直接打印纯文本， 所以需要对这类打印机语言进行特殊的处理。
====

[[printing-checking-parallel]]
===== 检测并口打印机

这部分内容将指导您怎样检测 FreeBSD 是否可以与一台已经连接在并口上的打印机通讯。

_要测试并口上的打印机：_

[.procedure]
====

. 用 man:su[1] 命令转换到 `root` 用户。
. 发送数据到打印机。
+
** 如果打印机可以直接打印纯文本， 可以用 man:lptest[1]。 输入：
+
[source,shell]
....
# lptest > /dev/lptN
....
+ 
其中 _N_ 是并口的编号， 从0开始。
** 如果打印机支持 PostScript(R) 或其他打印机语言， 可以发送一段小程序到打印机。 输入：
+
[source,shell]
....
# cat > /dev/lptN
....
+ 
然后， 一行一行地 _输入_ 输入这段程序。 因为在按下 `换行` 或者 `回车` 之后， 这一行就不能再修改了。 当您输入完这段程序之后， 按 `CONTROL+D`， 或者其他表示文件结束的键。
+ 
另外一种办法， 您可以把这段程序写在一个文件里， 并输入：
+
[source,shell]
....
# cat file > /dev/lptN
....
+ 
其中 _file_ 是包含这您要发给打印机程序的文件名。
====

之后， 您应该看到打印出了一些东西。 如果打印出的东西看起来并不正确， 请不要着急； 我们将在后面指导您如何解决这类问题。

[[printing-checking-serial]]
===== 检测串口打印机

这部分将告诉您如何检测 FreeBSD 是否可以与连接在串口上的打印机通讯。

_要测试连接在串口上的打印机：_

[.procedure]
====

. 通过 man:su[1] 命令转为 `root` 用户。
. 修改 [.filename]#/etc/remote# 这个文件。 增加下面这些内容：
+
[.programlisting]
....
printer:dv=/dev/port:br#bps-rate:pa=parity
....
+ 
其中 _port_ 是串口的设备节点 (`ttyu0`、 `ttyu1`， 等等)， _bps-rate_ 是与打印机通讯时使用的速率， 而 _parity_ 是通讯时打印机要求的校验方法 (应该是 `even`、 `odd`、 `none`， 或 `zero` 之一)。
+ 
这儿有一个串口打印机的例子， 它连接在第三个串口上， 速度为 19200 波特， 不进行校验：
+
[.programlisting]
....
printer:dv=/dev/ttyu2:br#19200:pa=none
....
+
. 用 man:tip[1] 连接打印机。 输入：
+
[source,shell]
....
# tip printer
....
+ 
如果没能成功， 则要再次修改 [.filename]#/etc/remote# 这个文件， 并且试试用 [.filename]#/dev/cuaaN# 代替 [.filename]#/dev/ttydN#。
. 发送数据到打印机。
+
** 如果打印机可以直接打印纯文本， 则用 man:lptest[1]。 输入：
+
[source,shell]
....
% $lptest
....
+
** 如果打印机支持 PostScript(R) 或者其他 打印机语言， 则发送一段小程序到 打印机。 一行一行的输入程序， 必须 _非常仔细_ 因为像退格 或者其他编辑键也许对打印机来说有它的 意义。 您同样也需要按一个特殊的 文件结束键， 让打印机知道它已经 接收了整个程序。 对于 PostScript(R) 打印机， 按 `CONTROL+D`。
+ 
或者， 您同样也可以把程序存储在一个文件里 并输入：
+
[source,shell]
....
% >file
....
+ 
其中 _file_ 是 包含要发送程序的文件名。 在 man:tip[1] 发送这个文件之后， 按代表 文件结束的键。
====

您应该看到打印出了一些东西。 如果它们看起来并不正确也不要着急； 我们将在稍后的章节中介绍如何解决这类问题。

[[printing-printcap]]
==== 启用后台打印： 文件 [.filename]#/etc/printcap#

目前， 您的打印机应该已经连好了线， 系统内核也为与打印机联机而重新配置好 (如果需要的话)， 而且您也已经可以发送一些简单的数据到打印机。 现在， 我们要配置 LPD 来使其控制您的打印机。

配置 LPD 要修改 [.filename]#/etc/printcap# 这个文件。 由于 LPD 后台打印系统在每次使用后台打印的时候， 都会读取这个文件， 因此对这个文件的修改会立即生效。

man:printcap[5] 这个文件的格式很简单。 您可以用您最喜欢的文本编辑器来修改 [.filename]#/etc/printcap# 这个文件。 这种格式和其他的像 [.filename]#/usr/shared/misc/termcap# 和 [.filename]#/etc/remote# 这类文件是一样的。 要得到关于这种格式的详尽信息， 请参阅联机手册 man:cgetent[3]。

简单的后台打印配置包括下面的几步：

[.procedure]
====

. 给打印机起一个名字 (记忆和使用的别名)， 然后把它们写进文件 [.filename]#/etc/printcap#； 参见 <<printing-naming,如何为打印机命名>> 这章来得到更多的关于起名的帮助。
. 通过增加 `sh` 项关掉报头页 (它默认是启用的)； 参见 <<printing-no-header-pages,如何禁用报头页>> 部分来得到更多信息。
. 建立一个后台打印队列的目录， 并且通过 `sd` 项目指定它的位置； 您可参见 <<printing-spooldir,创建后台打印队列目录>> 一节了解更多信息。
. 在 [.filename]#/dev# 下设置打印机设备节点， 并且在写在 [.filename]#/etc/printcap# 文件中 `lp` 项目里； 参见 <<printing-device,识别打印机设备>> 这部分可以得到更多信息。 此外， 如果打印机连接在串口上， 通讯参数的设置需要写在 `ms#` 项中。 这些参数在 <<printing-commparam,配置后台打印通讯参数>> 这在前面已经讨论过。
. 安装纯文本过滤器； 详情请参见 <<printing-textfilter,安装文本过滤器>> 小节。
. 用 man:lpr[1] 命令来测试设置。 想得到更多信息可以参见 <<printing-trying,测试>> 和 <<printing-troubleshooting,故障排除>> 部分。
====

[NOTE]
====
使用打印机语言的打印机， 如 PostScript(R) 打印机， 通常是不能直接打印纯文本的。 前面提到， 并且将在后面继续进行介绍的简单的设置方法， 均假定您正在安装这种只能打印它能识别的文件格式的打印机。
====

用户通常会希望直接在系统提供的打印机上打印纯文本。 采用 LPD 接口的程序也通常是这样设计的。 如果您正在安装这样一台打印机， 并且希望它不仅能打印使用它支持的打印机语言的任务 _而且_ 还能打印纯文本的任务的话， 那么强烈建议您在上面提到的简单设置的步骤上增加一步： 安装从自动纯文本到 PostScript(R) (或者其他打印机语言) 的转换程序。 更多的细节， 请参见 <<printing-advanced-if-conversion,在 PostScript(R) 打印机上打印纯文本>>。

[[printing-naming]]
===== 打印机的命名

第一步 (简单) 就是给打印机起一个名字。 您是按功能起名字还是干脆起个古怪的名字都没有关系， 因为您可以给打印机设置许多的别名。

在 [.filename]#/etc/printcap# 里至少有一个打印机必须指定， 别名是 `lp`。 这是默认的打印机名。 如果用户既没有 `PRINTER` 环境变量， 也没有在任何 LPD 命令的命令行中指定打印机名， 则 `lp` 将是默认要使用的打印机。

还有， 我们通常把最后一个别名设置成能完全描述打印机的名字， 包括厂家和型号。

一旦您选好了名字或者一些别名， 把它们放进文件 [.filename]#/etc/printcap# 里。 打印机的名字应该从最左边的一列写起。 用竖杠来隔开每个别名， 并且在最后一个别名后面加上一个冒号。

在下面的例子中， 我们从一个基本的 [.filename]#/etc/printcap# 开始， 它只定义了两台打印机 (一台 Diablo 630 行式打印机和一台 Panasonic KX-P4455 PostScript(R) 激光打印机 ):

[.programlisting]
....
#
#  /etc/printcap for host rose
#
rattan|line|diablo|lp|Diablo 630 Line Printer:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:
....

在这个例子中， 第一台打印机被命名为 `rattan` 并且设置了 `line`， `diablo`, `lp`， 和 `Diablo 630 Line Printer` 这几个别名。 因为它被设置了 `lp` 这个别名， 所以它是默认打印机。 第二台 被命名为 `bamboo`， 并且设置了 `ps`， `PS`, `S`， `panasonic`， 和 `Panasonic KX-P4455 PostScript v51.4` 这几个别名。

[[printing-no-header-pages]]
===== 不打印报头页

LPD 后台打印系统默认 会为每个任务打印 _报头页_。 报头页 包含了发送这个任务的用户， 发送这个任务的计算机， 任务的名字， 并用大字母打出。 但不幸的是， 所有这些额外的文本， 都会给在对打印机进行最初的配置时排除故障带来困难， 所以我们将先不打印报头页。

要暂停打印报头页， 为打印机的记录增加 `sh` 标记， 在 [.filename]#/etc/printcap# 文件中。 这儿有一个 [.filename]#/etc/printcap# 文件中使用 `sh` 的例子: 

[.programlisting]
....
#
#  /etc/printcap for host rose - no header pages anywhere
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:
....

注意我们的正确格式: 第一行从最左边一列开始， 而后的每一行用 TAB 缩进一次。 一行写不下需要换行时， 在换行前打一个反斜杠。

[[printing-spooldir]]
===== 建立后台打印队列目录

下一步设置就是要建立一个 _后台打印队列目录_， 也就是在打印任务最终完成之前用于存放这些任务的目录， 这个目录中也会存放后台打印系统用到的其他一些文件。

由于后台打印队列目录的变量本质， 通常 把这些目录安排在 [.filename]#/var/spool# 下。 您也没有必要去 备份后台打印队列目录里的内容。 重新建立它们只要简单的使用 man:mkdir[1] 命令。

通常， 我们习惯将目录名起成和 打印机一样的名字， 像下面 这样：

[source,shell]
....
# mkdir /var/spool/printer-name
....

然而， 如果您有很多网络打印机， 您可能想要把这些后台打印的队列目录目录放在一个单独的专为使用 LPD 打印而准备的目录里。 我们将用我们的两台打印机 `rattan` 和 `bamboo` 作为例子：

[source,shell]
....
# mkdir /var/spool/lpd
# mkdir /var/spool/lpd/rattan
# mkdir /var/spool/lpd/bamboo
....

[NOTE]
====
如果担心用户任务的保密性， 可能会希望保护相应的后台打印队列目录， 使之不能被其他用户访问。 后台打印的队列目录的属主应该是 daemon 用户， 而 `daemon` 用户和 `daemon` 组拥有读写和搜索的权限，但其他用户没有。 接下来用我们的两台打印机作为例子:

[source,shell]
....
# chown daemon:daemon /var/spool/lpd/rattan
# chown daemon:daemon /var/spool/lpd/bamboo
# chmod 770 /var/spool/lpd/rattan
# chmod 770 /var/spool/lpd/bamboo
....

====

最后， 您需要通过[.filename]##/etc/printcap## 文件告诉 LPD 这些目录。 您可以用 `sd` 标记来指定后台打印队列目录的路径:

[.programlisting]
....
#
#  /etc/printcap for host rose - added spooling directories
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:
....

注意打印机的名字要从第 1 列开始， 其他记录每行都要用 TAB 键缩进一次， 写不开需要换行在最后加上反斜杠。

如果您没用 `sd` 标记指定后台打印队列目录， 后台打印系统会将 [.filename]#/var/spool/lpd# 目录作为默认目录。

[[printing-device]]
===== 识别打印机设备

在 <<printing-hardware,Hardware Setup>> 一节中，我们说明了 FreeBSD 与打印机通讯将使用哪个端口和 [.filename]#/dev# 目录下的节点。 我们要告诉 LPD 这些信息。 当后台打印系统有任务需要打印，它将为过滤程序 （负责传送数据到打印机） 打开指定的设备。

用 `lp` 标记在 [.filename]#/etc/printcap# 里列出 [.filename]#/dev# 下的设备节点。

在我们的例子中， 假设打印机 `rattan` 在第一个并口上， 打印机 `bamboo` 在第六个串口上; 下面是 要对 [.filename]#/etc/printcap# 文件里增加的内容 :

[.programlisting]
....
#
#  /etc/printcap for host rose - identified what devices to use
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:\
        :lp=/dev/ttyu5:
....

如果您没在您的 [.filename]#/etc/printcap# 文件中 用 `lp` 标记指定设备节点， LPD 将默认使用 [.filename]#/dev/lp# 。 [.filename]#/dev/lp# 目前在 FreeBSD 中不存在。

如果您正在安装的打印机是连接在 并口上的， 请跳到 <<printing-textfilter,安装文本 过滤器>> 这章。 如果不是的话， 还是最好按下面介绍的 步骤做。

[[printing-commparam]]
===== 配置后台打印通讯参数

对于连在串口上的打印机， LPD 可以为发送数据到打印机的过滤程序设置好波特率， 校验， 和其他串口通讯参数 。 这是有利的， 因为:

* 它可以让您只需简单的修改 [.filename]#/etc/printcap# 就能尝试不同的通讯 参数; 您并不需要去重新编译过滤器 程序。
* 它使得后台打印系统可以在 多台有不同串口通讯设置的打印机上使用 相同的过滤器程序。

下面这个 [.filename]#/etc/printcap# 中 用 `lp` 标记来控制列出设备的 串口通讯参数 :

`br#bps-rate`::
设置设备的通讯速度为 _bps-rate_， 这里 _bps-rate_ 可以为 50， 75， 110, 134， 150， 200， 300， 600， 1200， 1800， 2400， 4800， 9600, 19200， 38400， 57600， or 115200 比特每秒。

`ms#stty-mode`::
设置已打开的中端设备的选项 。 man:stty[1] 将详细 讲述可用的选项。

当 LPD 打开 用 `lp` 指定的设备时， 它会 将设备的特性设置成在 `ms#` 标记后指定的那样。 特别是 `parenb`, `parodd`， `cs5`, `cs6`， `cs7`, `cs8`， `cstopb`, `crtscts`， 和 `ixon` 这些模式， 它们在 man:stty[1] 手册中有详细说明。

我们举个例子来添加我们连在第6个串口上的 打印机。 我们将设波特为38400。 至于模式， 我们将用 `-parenb` 设置成不校验， 用 `cs8` 设置成8位字符， 用 `clocal` 设置成不要调制解调器控制， 用 `crtscts` 设置成硬件流量控制：

[.programlisting]
....
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:
....

[[printing-textfilter]]
===== 安装文本过滤器

我们现在准备告诉 LPD 使用什么文本过滤器 给打印机发送任务。 __文本过滤器__， 也叫 __输入过滤器__， 是一个 在 LPD 有一个任务要发给 打印机时运行的程序。 当 LPD 为打印机运行文本过滤器时， 它设置过滤器的 标准输入为要发给打印机的任务， 而标准输出为 用 `lp` 标记指定的打印机 。 过滤器先从标准输入读取 任务， 为打印机进行一些转换 ， 并将结果写到标准输出， 这些结果 将被打印。 想得到更多关于文本过滤器的信息， 见 <<printing-advanced-filters,过滤器>> 这节。

对于简单的打印机设置， 文本过滤器可以仅仅是一段 执行 `/bin/cat` 的 shell 脚本来 发送任务到打印机。 FreeBSD 还提供了一个叫做 [.filename]#lpf# 的过滤器， 它可以处理退格和下划线来 使那些可能不能很好处理这类字符流的打印机正常工作。 而且， 当然， 您可以用任何其他的 您想用的过滤程序。 `lpf` 过滤器在 <<printing-advanced-lpf,lpf: 一个文本 过滤器>> 这节将有详细描述。

首先， 我们来写一段叫做 [.filename]#/usr/local/libexec/if-simple# 的简单 shell 脚本作为文本过滤器。 用您熟悉的文本编辑器将下面的内容放进 这个文件：

[.programlisting]
....
#!/bin/sh
#
# if-simple - Simple text input filter for lpd
# Installed in /usr/local/libexec/if-simple
#
# Simply copies stdin to stdout.  Ignores all filter arguments.

/bin/cat && exit 0
exit 2
....

使这个文件可以被执行：

[source,shell]
....
# chmod 555 /usr/local/libexec/if-simple
....

然后用 `if` 标记在 [.filename]#/etc/printcap# 里告诉 LPD 使用这个脚本。 我们将仍然为 一直作为例子的这两台打印机在 [.filename]#/etc/printcap# 里增加这个标记：

[.programlisting]
....
#
#  /etc/printcap for host rose - added text filter
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\ :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:\
        :if=/usr/local/libexec/if-simple:
....

[NOTE]
====
[.filename]#if-simple# 脚本的副本可以在 [.filename]#/usr/shared/examples/printing# 目录中找到。
====

===== 开启 LPD

man:lpd[8] 在 [.filename]#/etc/rc# 中被运行， 它是否被运行由 `lpd_enable` 这个变量控制。 这个 变量默认是 `NO`。 如果您还没有修改 ， 那么增加这行：

[.programlisting]
....
lpd_enable="YES"
....

到 [.filename]#/etc/rc.conf# 文件当中， 然后既可以重启您的 机器， 也可以直接运行 man:lpd[8]。

[source,shell]
....
# lpd
....

[[printing-trying]]
===== 测试

现在已经基本完成了 LPD 的基本设置。 但不幸的是， 还不是庆祝的时候， 因为我们还需要测试设置并且修正所有的 问题。 要测试设置， 尝试打印一些东西。 要 用 LPD 系统打印， 您可以 使用 man:lpr[1] 命令， 它可以提交一个任务来打印。

您可以联合使用 man:lpr[1] 和 man:lptest[1] 程序， 在 <<printing-testing,检查打印机 通讯>> 这节介绍怎样生成一些测试文本。

_要测试简单 LPD 设置：_

输入：

[source,shell]
....
# lptest 20 5 | lpr -Pprinter-name
....

其中 _printer-name_ 是 在 [.filename]#/etc/printcap# 中指定的打印机的一个名字 ( 或者一个别名) 。 要测试默认 打印机， 输入 man:lpr[1] 不带任何 `-P` 选项。 同样， 如果您正在测试一台使用 PostScript(R) 的打印机， 发送一个 PostScript(R) 程序到打印机而不是 使用 man:lptest[1]。 您可以把程序放在一个 文件里， 然后输入： `lpr file`。

对于一台 PostScript(R) 打印机， 您应该得到那段程序的 结果。 而如果您使用的 man:lptest[1]， 则您得到的 结果应该看起来像下面这样：

[source,shell]
....
!"#$%&'()*+,-./01234
"#$%&'()*+,-./012345
#$%&'()*+,-./0123456
$%&'()*+,-./01234567
%&'()*+,-./012345678
....

要更进一步的测试打印机， 尝试下载一些大的 程序 (为基于特定语言的打印机 ) 或者运行 man:lptest[1] 并使用不同的参数。 比如， `lptest 80 60` 将生成 60 行 每行 80 个字符。

如果打印机不能工作， 参考 <<printing-troubleshooting,故障排除>> 这节。

[[printing-advanced]]
== 高级设置

[WARNING]
====

从 FreeBSD 8.0 起， 串口对应的设备名由 [.filename]#/dev/ttydN# 变为 [.filename]#/dev/ttyuN#。 FreeBSD 7.X 用户应将这篇文档的示例中的设备名改为原先的样子。
====

这部分将描述用来打印特别格式文件， 页眉， 通过网络打印， 以及对打印机使用限制和 记帐。

[[printing-advanced-filter-intro]]
=== 过滤器

尽管 LPD 处理网络协议， 任务排队， 访问控制， 和打印的其他方面， 但大部分 _实际_ 工作还是由 _过滤器_。 过滤器是 一种与打印机通讯并且处理设备依赖和特殊需要的 程序。 在简单打印机设置这节里， 我们安装了一个纯文本过滤器 - 一个应该可以用在大多数打印机上的极简单的过滤器 (<<printing-textfilter, 安装文本过滤器>>)。

然而，为了进行格式转换， 打印记帐， 适应特殊的打印机， 等等， 您需要明白过滤器是怎样工作的。 在根本上过滤器负责处理这些方面。 但坏消息是大多数时候 _您_ 必须自己提供过滤器。 好消息是很多过滤器通常都已经有了; 当没有的时候， 它们通常也是很好写的。

FreeBSD 也提供了一个过滤器， [.filename]#/usr/libexec/lpr/lpf#， 可以让大多数可以打印纯文本的打印机工作。 ( 它处理文件里的退格和跳格，并且进行记帐， 但这基本就是它所有能做的了。) 这里还有几个过滤器和过滤器组件在 FreeBSD Ports Collection 里。

这是在这节里您将找到的内容：

* 在 <<printing-advanced-filters,过滤器是如何工作的>> 小节中将介绍在打印过程中过滤器的作用。 如果希望了解在 LPD 使用过滤器时， 在 "幕后" 发生的事情， 便应阅读这一小节。 了解这些知识能够帮助您在为打印机安装过滤器时更快地排查可能会遇到的各种问题。
* LPD 假定任何打印机在默认状态下均能打印纯文本内容。 对于不能直接打印纯文本的 PostScript(R) 打印机 (以及其他基于打印语言的打印机) 而言这会带来问题。 在 <<printing-advanced-if-conversion,在 PostScript(R) 打印机上使用纯文本任务>> 这节中将会介绍如何解决这个问题的方法。 如果您使用 PostScript(R) 打印机， 就应阅读这节内容。
* PostScript(R) 对于许多程序来说都是一个非常受欢迎的输出格式。 一些人甚至直接写 PostScript(R) 代码。 但不幸的是， PostScript(R) 打印机非常昂贵。 <<printing-advanced-ps,模拟 PostScript(R) 在 非 PostScript(R) 打印机上>> 这节将告诉您怎样进一步修改 打印机的文本过滤器， 使得一台 _非 PostScript(R)_ 打印机接受 并打印 PostScript(R) 数据。 如果 您没有 PostScript(R) 打印机， 那么您应该阅读这个小节。
* <<printing-advanced-convfilters,转换过滤器>> 这节讲述了一个自动把指定格式文件， 比如图像或排版数据， 转换成您打印机可以理解的格式的方法。 在阅读了这节之后， 您就应该可以配置打印机， 让用户可以用 `lpr -t` 来打印 troff 数据、 用 `lpr -d` 来打印 TeX DVI 数据， 或用 `lpr -v` 来打印光栅图像数据等工作了。 建议您阅读这节。
* <<printing-advanced-of,输出 过滤器>> 这节讲述了这个不是经常使用的 LPD: 的功能－输出过滤器。 除非您要打印页眉 (见 <<printing-advanced-header-pages,页眉>> 这节 )， 您或许可以完全跳过这节。
* <<printing-advanced-lpf,lpf: 一个文本过滤器>> 描述了 `lpf`， 一个 FreeBSD 自带的相当完整而又简单的文本过滤器， 可以使用在行式打印机(和那些担当行式打印机功能的激光打印机 )上。 如果您需要一个快速的方法来让打印机统计打印纯文本的工作量， 或者您有一台遇到退格字符就冒烟的打印机， 您应该考虑 `lpf`。

[NOTE]
====
您可以在 [.filename]#/usr/shared/examples/printing# 目录中找到下面将提到的那些脚本的副本。
====

[[printing-advanced-filters]]
==== 过滤器是怎样工作的

前面说过， 过滤器是一个被 LPD 启动， 用来处理与打印机通讯过程中设备依赖的部分 的可执行程序。

当 LPD 想要打印 一个任务中的文件， 它启动一个过滤器 程序。 它把要打印的文件设置成过滤器的标准输入， 标准输出设置成打印机， 并且把错误信息定向到 错误日志文件 (在 `lf` 标识里指定， 默认在 [.filename]#/etc/printcap#， 或者 [.filename]#/dev/console# 文件里 )。

过滤器被 LPD 启动， 并且过滤器的参数依赖于 [.filename]#/etc/printcap# 文件中所列出的和用户为任务用 man:lpr[1] 命令所指定的。 例如， 如果用户输入 `lpr -t`， LPD 会启动 troff 过滤器， 即在目标打印机的 `tf` 标签里所列出的过滤器。 如果用户想要打印纯文本， 它将会启动 `if` 过滤器 (这是通常的情况： 参见 <<printing-advanced-of,输出过滤器>> 来得到 细节)。

在 [.filename]#/etc/printcap# 文件中， 您可以指定三种过滤器：

* The _文本过滤器_， 在 LPD 文档中也叫做 _输入过滤器_ ， 处理 常规的文本打印。 可以把它想象成默认过滤器。 LPD 假定每台打印机默认情况下都可以打印纯文本， 而文本过滤器的任务就是来搞定退格、 跳格， 或者其他在某种打印机上容易错误的特殊字符。 如果您所在的环境对打印机的使用情况进行记帐， 那么文本过滤器必须也对打印的页数进行统计， 通常是根据打印的行数和打印机在每页上能打印的行数进行计算得出。 文本过滤器的启动命令为：
+
`filter-name` [ -c ] -w _width_ -l _length_ -i _indent_ -n _login_ -h _host_ _acct-file_
+ 
这里
+
`-c`:::
当任务用 `lpr -l` 这个命令提交时出现 

_width_:::
这里取您在 [.filename]#/etc/printcap# 文件中指定的 `pw` (页 宽) 标签的值， 默认为 132。

_length_:::
这里取您的 `pl` (页 长) 标签的值， 默认为 66

_indent_:::
这里是来自 `lpr -i` 命令的总缩进量， 默认为 0

_login_:::
这里是正在打印文件的用户名 

_host_:::
这里是提交打印任务的主机名 

_acct-file_:::
这里是来自 `af` 变量中指定的用于记帐的文件名。

* _转换过滤器_ 的功能是， 将特定格式的文件转换成打印机能够识别并打印的格式。 例如， ditroff 格式的排版数据就是无法直接打印的， 但您可以安装一个转换过滤器来将 ditroff 文件转换成一种打印机可以识别和打印的形式。 请参见 <<printing-advanced-convfilters,转换过滤器>> 这一节来了解更多细节。 如果您需要对打印进行记帐， 那么转换过滤器也必须完成记帐工作。 转换过虑器的启动命令为：
+
`filter-name` -x _pixel-width_ -y _pixel-height_ -n _login_ -h _host_ _acct-file_
+ 
这其中 _pixel-width_ 的值来自 `px` 标签 (默认为 0)， 而 _pixel-height_ 的值来自 `py` 标签 (默认为 0)。
* _输出过滤器_ 仅在没有文本过滤器时， 或者报头页被打开时使用。 就我们的经验而言， 输出过滤器是很少用到的. 在 <<printing-advanced-of,输出过滤器>> 这节中会介绍它们。 启动输出过滤器的命令行只有两个参数：
`filter-name` -w _width_ -l _length_
+ 
它们的作用与文本过滤器的 `-w` 和 `-l` 参数是一样的。

过滤器也应该在 _退出_ 时给出下面的几种退出状态:

exit 0::
过滤器已经成功的打印了文件. 

exit 1::
过滤器打印失败了， 但希望 LPD 试着再打印一次。 如果过滤器返回了这个状态， LPD 将重新启动过滤器。

exit 2::
过滤器打印失败并且不希望 LPD 重试。 这种情况下 LPD 会放弃这个文件。

文本过滤器随 FreeBSD 一起发布， 文件名为 [.filename]#/usr/libexec/lpr/lpf#， 它利用页宽和页长参数来决定何时发送送纸指令， 并提供位打印记帐的方法。 它使用登录名、 主机名， 和记帐文件参数来生成记帐记录。

如果您想购买过滤器， 要注意它是否是与 LPD 兼容。 如果兼容的话， 则它们必须支持前面提到的那些参数。 如果您打算编写普通的过滤器程序， 则同样需要使之支持前面那些参数和退出状态码。

[[printing-advanced-if-conversion]]
==== 在 PostScript(R) 打印机上打印纯文本任务

如果您是您的计算机和 PostScript(R) (或其他语言的) 打印机的唯一用户， 而且您不打算发送纯文本到打印机， 并因此不打算从应用程序程序直接将纯文本发到打印机的话， 就完全不需要再关心这节的内容了。

但是， 如果打印机同时需要接收 PostScript(R) 和纯文本的任务， 就需要对打印机进行设置了。 要完成这项工作， 我们需要一个文本过滤器来检测到达的任务是纯文本的还是 PostScript(R) 格式的。 所有 PostScript(R) 的任务必须以 `%!` (其他打印机语言请参见打印机的文档) 开头。 如果任务的头两个字符是这两个， 就代表这是 PostScript(R) 格式的， 并且可以直接略过任务剩余的部分。 如果任务开头的两个字符不是这两个， 那么过滤器将把文本转换成 PostScript(R) 并打印结果。

我们怎样去做？

如果你有一台串口打印机， 一个好办法就是安装 `lprps`。 `lprps` 是一个可以与打印机进行双向通信 PostScript(R) 打印机过滤器。 它用打印机传来的详细信息来更新打印机的状态文件， 所以用户和管理员可以准确的看到打印机处在什么样的状态 (比如 `缺墨` 或者 `卡纸`)。 但更重要的是， 它包含了一个叫做 `psif` 的程序， 它可以检测接收到的文件是否是纯文本的， 并且将使用 `textps` 命令 ( 也是由 `lprps` 提供的程序) 转换文本到 PostScript(R)。 然后它会用 `lprps` 将任务发送到打印机。

`lprps` 可以在 FreeBSD Ports Collection (详见 crossref:ports[ports,The Ports Collection]) 中找到。 你可以根据页面的尺寸选择安装 package:print/lprps-a4[] 和 package:print/lprps-letter[]。 在安装了 `lprps` 之后， 只需指定 `psif` 这个程序的路径， 这也是包含在 `lprps` 中的一个程序。 如果您已经用 ports 安装好了 `lprps`， 将下面的内容添加到 [.filename]#/etc/printcap# 文件中 PostScript(R) 打印机的记录部分中：

[.programlisting]
....
:if=/usr/local/libexec/psif:
....

同时还需要指定 `rw` 标签来告诉 LPD 使用读-写模式打开打印机。

如果您有一台并口的 PostScript(R) 打印机 (因此不能与打印机进行 `lprps` 需要的双向通信)， 可以使用下面这段 shell 脚本来充当文本过滤器:

[.programlisting]
....
#!/bin/sh
#
#  psif - Print PostScript or plain text on a PostScript printer
#  Script version; NOT the version that comes with lprps
#  Installed in /usr/local/libexec/psif
#

IFS="" read -r first_line
first_two_chars=`expr "$first_line" : '\(..\)'`

if [ "$first_two_chars" = "%!" ]; then
    #
    #  PostScript job， print it.
    #
    echo "$first_line" && cat && printf "\004" && exit 0
    exit 2
else
    #
    #  Plain text， convert it， then print it.
    #
    ( echo "$first_line"; cat ) | /usr/local/bin/textps && printf "\004" && exit 0
    exit 2
fi
....

在上面的脚本中， `textps` 命令是一个独立安装的程序用来将纯文本转换成 PostScript(R)。 您可以使用任何您喜欢的文本到 PostScript(R) 转换程序。 FreeBSD Ports Collection (详见 crossref:ports[ports,Ports Collection]) 中包含了一个功能非常完整的文本到 PostScript(R) 的转换程序， 它叫做 `a2ps`。

[[printing-advanced-ps]]
==== 模拟 PostScript(R) 在非 PostScript(R) 打印机上

PostScript(R) 是高质量排版和打印 _事实上的_ 标准。 而 PostScript(R) 也是一个 _昂贵_ 的标准。 幸好， Aladdin 开发了一个和 PostScript(R) 类似的叫做 Ghostscript 的程序可以用在 FreeBSD 上。 Ghostscript 可以读取大多数 PostScript(R) 的文件并处理其中的页面交给多种设备，包括许多品牌的非 PostScript(R) 打印机。 通过安装 Ghostscript 并使用一个特殊的文本过滤器，则可以使一台非 PostScript(R) 打印机用起来就像真的 PostScript(R) 打印机一样。

Ghostscript 被收录在 FreeBSD Ports Collection 中，有许多可用的版本， 比较常用的版本是 package:print/ghostscript-gpl[]。

要模拟 PostScript(R)， 文本过滤器要检测是否要打印一个 PostScript(R) 文件。 如果不是， 那么过滤器将直接将文件发送到打印机； 否则， 它会用 Ghostscript 先将文件转换成打印机可以理解的格式。

这里有一个例子: 下面的脚本是一个针对 Hewlett Packard DeskJet 500 打印机的文本过滤器。 对于其他打印机， 替换 `gs` (Ghostscript) 命令中的 `-sDEVICE` 参数就可以了。 (输入 `gs -h` 来获得当前安装的 Ghostscript 所支持的设备列表。)

[.programlisting]
....
#!/bin/sh
#
#  ifhp - Print Ghostscript-simulated PostScript on a DeskJet 500
#  Installed in /usr/local/libexec/ifhp

#
#  Treat LF as CR+LF (to avoid the "staircase effect" on HP/PCL
#  printers):
#
printf "\033&k2G" || exit 2

#
#  Read first two characters of the file
#
IFS="" read -r first_line
first_two_chars=`expr "$first_line" : '\(..\)'`

if [ "$first_two_chars" = "%!" ]; then
    #
    #  It is PostScript; use Ghostscript to scan-convert and print it.
    #
    /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \
      -sOutputFile=- - && exit 0
else
    #
    #  Plain text or HP/PCL， so just print it directly; print a form feed
    #  at the end to eject the last page.
    #
    echo "$first_line" && cat && printf "\033&l0H" &&
exit 0
fi

exit 2
....

最后， 需要告知 LPD 所使用的过滤器， 通过 `if` 标签完成:

[.programlisting]
....
:if=/usr/local/libexec/ifhp:
....

您可以输入 `lpr plain.text` 和 `lpr whatever.ps`， 它们都应该可以成功打印。

[[printing-advanced-convfilters]]
==== 转换过滤器

在完成了 <<printing-simple, 打印机简单设置 >> 这节中所描述的内容之后， 头一件事 恐怕就是为你喜爱的格式的文件安装转换过滤器了 (除了纯 ASCII 文本)。

===== 为什么安装转换过滤器?

转换过滤器使打印众多格式的文件变得很容易。 比如， 假设我们大量使用 TeX 排版系统， 并且有一台 PostScript(R) 打印机。 每次从 TeX 生成一个 DVI 文件， 我们都不能直接打印它直到我们将 DVI 文件转换成 PostScript(R)。 转换的命令应该是下面的样子:

[source,shell]
....
% dvips seaweed-analysis.dvi
% lpr seaweed-analysis.ps
....

通过安装 DVI 文件的转换过滤器， 我们可以跳过每次手动转换这一步， 而让 LPD 来完成这个步骤。 现在， 每次要打印 DVI 文件， 我们只需要一步就可以打印它：

[source,shell]
....
% lpr -d seaweed-analysis.dvi
....

我们要 LPD 转换 DVI 文件是通过指定 `-d` 选项完成的。 <<printing-lpr-options-format,格式和转换 选项>> 这一节列出了所有的转换选项。

对于每种想要打印机支持的转换， 首先要安装 _f转换过滤器_ 然后在 [.filename]#/etc/printcap# 中指定它的路径。 在简单打印设置中， 转换过滤器类似于文本过滤器 (详见 <<printing-textfilter, 安装文本过滤器 >>) 不同的是它不是用来打印纯文本， 而是将一个文件转换成打印机能够理解的格式。

===== 我应该安装哪个转换过滤器？

您应该安装您希望使用的转换过滤器。 如果要打印很多 DVI 数据， 就需要 DVI 转换过滤器； 如果有大量的 troff 数据， 就应该安装 troff 过滤器。

下面的表格总结了可以与 LPD配合 工作的过滤器， 以及它们在 [.filename]##/etc/printcap##文件中的变量名， 还有如何在 ``lpr``命令中调用它们：

[.informaltable]
[cols="1,1,1", frame="none", options="header"]
|===
| 文件类型
| 在/etc/printcap文件中的变量名
| 在lpr命令中调用使用的参数

|cifplot
|`cf`
|`-c`

|DVI
|`df`
|`-d`

|plot
|`gf`
|`-g`

|ditroff
|`nf`
|`-n`

|FORTRAN text
|`rf`
|`-f`

|troff
|`tf`
|`-f`

|raster
|`vf`
|`-v`

|plain text
|`if`
|none, `-p`, or `-l`
|===

在例子中， ``lpr -d``就是指 打印机需要在[.filename]##/etc/printcap##文件中 ``df``变量所指的过滤器。

不管别人怎么说， 像 FORTRAN 的文本 和 plot 这些格式已经基本不用了。 所以在您的机器上， 就可以安装其他的过滤器来替换这些参数原有的意义。 例如， 假设想要能直接打印 Printerleaf 文件 (由 Interleaf desktop publishing 程序生成)， 而且不打算打印 plot 文件， 就可以安装一个 Printerleaf 转换过滤器并且用 `gf` 变量指定它。 然后就可以告诉您的用户使用 `lpr -g` 就可以 "打印 Printerleaf 文件。"

===== 安装转换过滤器

以为安装的转换过滤器不是 FreeBSD 基本系统的一部分， 所以它们可能是在 [.filename]#/usr/local# 目录下。 通常目录 [.filename]#/usr/local/libexec# 是保存它们的地方， 因为它们通常是通过 LPD 运行的； 普通用户应该并不需要直接运行它们。

要启用一个转换过滤器， 只需要在 [.filename]#/etc/printcap# 文件中为目标打印机中合适的变量赋上过滤器所在的路径。

在接下来的例子当中， 我们将为 一台叫做 `bamboo` 的打印机添加一个转换过滤器。 下面是这个例子的 [.filename]#/etc/printcap# 文件， 其中使用新变量 `df` 来为打印机 `bamboo` 设置转换过滤器：

[.programlisting]
....
#
#  /etc/printcap for host rose - added df filter for bamboo
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:rw:\
        :if=/usr/local/libexec/psif:\
        :df=/usr/local/libexec/psdf:
....

这里的 DVI 过滤器是一段 shell 脚本， 名字叫做 [.filename]#/usr/local/libexec/psdf#。 下面是它的代码：

[.programlisting]
....
#!/bin/sh
#
#  psdf - DVI to PostScript printer filter
#  Installed in /usr/local/libexec/psdf
#
# Invoked by lpd when user runs lpr -d
#
exec /usr/local/bin/dvips -f | /usr/local/libexec/lprps "$@"
....

这段脚本以过滤器模式运行 `dvips` (参数 `-f` ) 并从标准输入读取要打印的任务。 然后运行 PostScript(R) 文本过滤器 `lprps` (详见 <<printing-advanced-if-conversion,在 PostScript(R) 打印机上打印纯文本任务>> 这一节)， 并且带着 LPD 传给脚本的全部参数。 `lprps` 工具将利用这些参数来为打印进行记帐。

===== 更多转换过滤器应用实例

因为安装转换过滤器的步骤并不是固定的， 所以这节介绍了一些可行的例子。 在以后的安装配置过程中可以以这些例子为参考。 甚至如果合适的话， 可以完全照搬过去。

这段例子中的脚本是一个 Hewlett Packard LaserJet III-Si 打印机的光栅格式数据 (实际上也就是 GIF 文件)：

[.programlisting]
....
#!/bin/sh
#
#  hpvf - Convert GIF files into HP/PCL, then print
#  Installed in /usr/local/libexec/hpvf

PATH=/usr/X11R6/bin:$PATH; export PATH
giftopnm | ppmtopgm | pgmtopbm | pbmtolj -resolution 300 \
    && exit 0 \
    || exit 2
....

它的工作原理就是将 GIF 文件转换成 portable anymap， 再转换成 portable graymap， 然后再转换成 portable bitmap， 最后再转换成 LaserJet/PCL- 兼容的数据。

下面是为打印机配置上上述过滤器的 [.filename]#/etc/printcap# 文件：

[.programlisting]
....
#
#  /etc/printcap for host orchid
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
        :lp=/dev/lpt0:sh:sd=/var/spool/lpd/teak:mx#0:\
        :if=/usr/local/libexec/hpif:\
        :vf=/usr/local/libexec/hpvf:
....

下面的脚本是一个在名叫 `bamboo` 的这台 PostScript(R) 打印机上打印用 groff 排版软件生成的 troff 数据的打印过滤器：

[.programlisting]
....
#!/bin/sh
#
#  pstf - Convert groff's troff data into PS, then print.
#  Installed in /usr/local/libexec/pstf
#
exec grops | /usr/local/libexec/lprps "$@"
....

上面这段脚本还是用 `lprps` 来与打印机进行通讯。 如果打印机是接在并口上的， 那么就应该使用下面的这段脚本：

[.programlisting]
....
#!/bin/sh
#
#  pstf - Convert groff's troff data into PS, then print.
#  Installed in /usr/local/libexec/pstf
#
exec grops
....

这里是我们要启用过滤器需要在 [.filename]#/etc/printcap# 里增加的内容：

[.programlisting]
....
:tf=/usr/local/libexec/pstf:
....

下面的例子也许会让许多 FORTRAN 老手羞愧。 它是一个 FORTRAN- 文本 的过滤器， 能在任意一台 可以打印纯文本的打印机上使用。 我们将为打印机 `teak` 安装这个过滤器：

[.programlisting]
....
#!/bin/sh
#
# hprf - FORTRAN text filter for LaserJet 3si:
# Installed in /usr/local/libexec/hprf
#

printf "\033&k2G" && fpr && printf "\033&l0H" &&
 exit 0
exit 2
....

然后我们要在 [.filename]#/etc/printcap# 中为打印机能够 `teak` 启用这个过滤器添加下面的内容：

[.programlisting]
....
:rf=/usr/local/libexec/hprf:
....

最后， 再给出一个有些复杂的例子。 我们将给以前介绍过的 `teak` 这台激光打印机添加一个 DVI 过滤器。 首先， 最容易的部分： 更新 [.filename]#/etc/printcap# 加入 DVI 过滤器的路径：

[.programlisting]
....
:df=/usr/local/libexec/hpdf:
....

现在， 该困难的部分了： 编写过滤器。 为了实现过滤器， 我们需要一个 DVI-到-LaserJet/PCL 转换程序。 FreeBSD Ports Collection (详见 crossref:ports[ports,Ports Collection] 这一节) 中有一个： package:print/dvi2xx[]。 安装这个 port 就会得到我们需要的程序， `dvilj2p` ， 它可以将 DVI 数据转换成 LaserJet IIp， LaserJet III， 和 LaserJet 2000 兼容的数据。

`dvilj2p` 工具使得过滤器 `hpdf` 变得十分复杂， 因为 `dvilj2p` 不能读取标准输入。 它需要从文件中读取数据。 更糟糕的是， 这个文件的名字必须以 [.filename]#.dvi# 结尾。 所以使用 [.filename]#/dev/fd/0# 作为标准输入是有问题的。 我们可以通过连接 (符号连接) 来解决这个问题。 连接一个临时的文件名 (一个以 [.filename]#.dvi# 结尾的文件名) 到 [.filename]#/dev/fd/0#， 从而强制 `dvilj2p` 从标准输入读取。

现在迎面而来的是另外一个问题， 我们不能使用 [.filename]#/tmp# 存放临时连接。 符号连接是被用户和组 `bin` 拥有的。 而过滤器则是以 `daemon` 用户运行的。 并且 [.filename]#/tmp# 目录设置了 sticky 位。 所以过滤器只能建立符号连接， 但它不能在用完之后清除掉这些连接。 因为它们属于不同的用户。

所以过滤器将在当前工作目录下建立符号连接， 即后台打印队列目录 (用变量 `sd` 在 [.filename]#/etc/printcap# 中指定)。 这是一个非常好的让过滤器完成它工作的地方， 特别还是因为 (有时) 这个目录比起 [.filename]#/tmp# 来有更多的可用磁盘空间。

最后， 给出过滤器的代码：

[.programlisting]
....
#!/bin/sh
#
#  hpdf - Print DVI data on HP/PCL printer
#  Installed in /usr/local/libexec/hpdf

PATH=/usr/local/bin:$PATH; export PATH

#
#  Define a function to clean up our temporary files.  These exist
#  in the current directory, which will be the spooling directory
#  for the printer.
#
cleanup() {
   rm -f hpdf$$.dvi
}

#
#  Define a function to handle fatal errors: print the given message
#  and exit 2.  Exiting with 2 tells LPD to do not try to reprint the
#  job.
#
fatal() {
    echo "$@" 1>&2
    cleanup
    exit 2
}

#
#  If user removes the job, LPD will send SIGINT, so trap SIGINT
#  (and a few other signals) to clean up after ourselves.
#
trap cleanup 1 2 15

#
#  Make sure we are not colliding with any existing files.
#
cleanup

#
#  Link the DVI input file to standard input (the file to print).
#
ln -s /dev/fd/0 hpdf$$.dvi || fatal "Cannot symlink /dev/fd/0"

#
#  Make LF = CR+LF
#
printf "\033&k2G" || fatal "Cannot initialize printer"

#
#  Convert and print.  Return value from dvilj2p does not seem to be
#  reliable, so we ignore it.
#
dvilj2p -M1 -q -e- dfhp$$.dvi

#
#  Clean up and exit
#
cleanup
exit 0
....

[[printing-advanced-autoconv]]
===== 自动转换： 一种替代转换过滤器的方法

以上这些转换过滤器基本上建成了您的打印环境， 但也有不足就是必须由用户来指定 (在 man:lpr[1] 命令行中) 要使用哪一个过滤器。 如果您的用户不是对计算机很在行， 那么选用过滤器将是一件麻烦的事情。 更糟的是， 当过滤器设定的不正确时， 过滤器被用在了不它对应类型的文件上， 打印机也许会喷出上百张纸。

比只安装转换过滤器更好的方法， 就是让文本过滤器 (因为它是默认的过滤器) 来检测要打印文件的类型， 然后自动运行正确的转换过滤器。 像 `file` 这样的工具可以给我们一定的帮助。 当然， 要区分开 _有些_ 文件的类型还是有困难的 - 但是， 当然， 您可以仅为它们提供转换过滤器。

FreeBSD 的 Ports 套件提供了一个可以自动进行转换的文本过滤器， 名字叫做 `apsfilter` (package:print/apsfilter[])。 它可以检测纯文本、 PostScript(R)、 DVI 以及几乎任何格式的文件， 并在执行相应的转换之后完成打印工作。

[[printing-advanced-of]]
==== 输出过滤器

LPD 后台打印系统还支持一种我们还没有讨论过的过滤器： 输出过滤器。 输出过滤器只是用来打印纯文本的， 类似于文本过滤器， 但简化了许多地方。 如果您正在使用输出过滤器而不是文本过滤器， 那么：

* LPD 为整个任务启动一个输出过滤器， 而不是为任务中的每个文件都启动一次。
* LPD 不会提供任务中文件开始和结束的信息给输出过滤器。
* LPD 不会提供用户名或者主机名给过滤器， 所以它是无法做打印记帐的。 事实上它只有两个参数：
+
`过滤器-名字` -w__宽度__ -l__长度__
+ 
_宽度_ 来自于 `pw` 变量， 而 _length_ 来自于 `pl` 变量， 这些值都是实际问题中给打印机设置的。

不要让输出过滤器的简化所耽误。 如果想要输出过滤器完成让任务中的每个文件都重新开始一页打印是 _不可能_ 的。 请使用文本过滤器 (也叫输入过滤器)； 详见 <<printing-textfilter,安装文本过滤器>>。 此外， 实际上， 输出过滤器 _更复杂_ ， 它要检查发给它的字节流中是否有特殊的标志字符， 并且给自己发送信号来代替 LPD 的。

可是， 如果打算要报头页或者需要发送控制字符或者其他的初始化字符串来完成打印报头页， 那么输出过滤器则是 _必需的_。 (但是它也是 _无用的_ 如果打算对打印的用户计费， 因为 LPD 不会给输出过滤器任何用户或者主机的信息。)

在一台单个的打印机上， LPD 同时允许输出过滤器、 文本过滤器和其他的过滤器。 在某些情况下， LPD 将仅会启动输出过滤器来打印报头页 (详见 <<printing-advanced-header-pages,报头页>>)。 然后 LPD 会要求输出过滤器 _自己停止运行_ ， 它发送给过滤器两个字节： ASCII 031跟着一个 ASCII 001。 当输出过滤器看见这两个字节 (031, 001)， 它应该通过发送 `SIGSTOP` 信号来停止自己的运行。 当 LPD 已经运行好了其他的过滤器， 它会通过给输出过滤器发送 `SIGCONT` 信号来让输出过滤器重新运行。

如果仅有一个输出过滤器而 _没有_ 文本过滤器， 并且 LPD 正在处理一个纯文本任务， LPD 会使用输出过滤器来完成这个任务。 像以前运行一样， 输出过滤器会按顺序打印任务中的文件， 而不会插入送纸或其他进纸的命令， 但这也许并 _不是_ 您想要的结果。 在大多数情况下， 您还是需要一个文本过滤器。

`lpf` 这个我们前面介绍过的文本过滤器程序， 也可以用来做输出过滤器。 如果需要使用快速且混乱的输出过滤器， 但又不想写字节检测和信号发送代码， 那么试试 `lpf`。 `lpf` 也可以包含在一个 shell 脚本中来处理任何打印机可能需要的初始化代码。

[[printing-advanced-lpf]]
==== `lpf`： 一个文本过滤器

[.filename]#/usr/libexec/lpr/lpf# 这个程序包含在 FreeBSD 的二进制程序中， 它是一个文本过滤器 (输入过滤器)。 它可以缩排输出 (用 `lpr -i` 命令提交的任务)， 可以打印控制字符禁止断页用 `lpr -l` 提交的任务)， 可以调整任务中退格和制表符打印的位置， 还可以对打印进行记帐。 它同样可以像输出过滤器一样工作。

`lpf` 适用于很多打印环境。 尽管它本身没有向打印机发送初始化代码的功能， 但写一个 shell 脚本来完成所需的初始化并执行 `lpf` 是很容易的。

为了让 `lpf` 可以正确的进行打印记帐， 那么需要 [.filename]#/etc/printcap# 中的 `pw` 和 `pl` 变量都填入正确的值。 它用这些值来测定一页能打印多少文本， 并计算出任务有多少页。 想得到更多关于打印记帐的信息， 请参见 <<printing-advanced-acct,对打印机使用进行记帐>>。

[[printing-advanced-header-pages]]
=== 报头页

如果您有 _很多_ 用户， 他们正在使用各式各样的打印机， 那么您或许要考虑一下把 _报头页_ 当作无可避免之灾祸了。

报头页， 也叫 _banner_ 或者 _burst 页_， 可以用来辨别打印出的文件是谁打印的。 它们通常用大号的粗体字母打印出来， 也可能用装饰线围绕四周， 所以在一堆打印出的文件中， 突出的显示了这个文件属于哪个用户的哪个任务。 这可以让用户快速的找到他们的任务。 而报头页一个明显的缺点就是， 在每个任务中都要有一张或者几张纸作为报头页印出来， 可是它们的有用的地方只发挥几分钟的作用， 最后它们会被放进回收站或者扔进垃圾堆。 (注意报头页只是一个任务一个， 而不是任务中的每个文件都有一个， 所以可能对纸张还不算很浪费。)

LPD 系统可以自动为您的打印提供报头页， _如果_ 您的打印机可以直接打印纯文本。 如果您的打印机是一台 PostScript(R) 打印机， 您将需要一个外部的程序来生成报头页； 详见 <<printing-advanced-header-pages-ps,在 PostScript(R) 打印机上打印报头页>>。

[[printing-advanced-header-pages-enabling]]
==== 打开报头页

在 <<printing-simple,简单打印设置>> 这节， 我们通过在 [.filename]#/etc/printcap# 文件中指定 `sh` ("禁止报头页") 来把报头页功能关掉了。 要重新为打印机开启报头页功能， 只需要删除掉 `sh`。

听起来很容易， 不是么？

是的。 您 _可能_ 不得不让输出过滤器来给打印机发送初始化字符串。 下面是一个用在 Hewlett Packard PCL-兼容打印机上的输出过滤器的例子：

[.programlisting]
....
#!/bin/sh
#
#  hpof - Output filter for Hewlett Packard PCL-compatible printers
#  Installed in /usr/local/libexec/hpof

printf "\033&k2G" || exit 2
exec /usr/libexec/lpr/lpf
....

用 `of` 变量指定输出过滤器的路径。 参见 <<printing-advanced-of,输出过滤器>> 这一节来得到更多信息。

下面是一个为我们以前介绍的叫做 `teak` 的打印机配置的 [.filename]#/etc/printcap# 文件； 在配置当中我们开启了报头页并且加入了上述的打印过滤器：

[.programlisting]
....
#
#  /etc/printcap for host orchid
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
        :lp=/dev/lpt0:sd=/var/spool/lpd/teak:mx#0:\
        :if=/usr/local/libexec/hpif:\
        :vf=/usr/local/libexec/hpvf:\
        :of=/usr/local/libexec/hpof:
....

现在， 当用户再发任务给打印机 `teak` 的时候， 每个任务都会有一个报头页。 如果用户想要花时间来寻找他们自己打印的文件， 那么他们可以通过 `lpr -h` 命令来提交任务； 参考 <<printing-lpr-options-misc,报头页选项>> 这一节来得到更多关于 man:lpr[1] 的选项。

[NOTE]
====
LPD 在报头页之后发出一个换纸字符。 如果您的打印机使用一个不同的字符或者字符串当作退纸指令， 在 [.filename]#/etc/printcap# 中用 `ff` 变量指定即可。
====

[[printing-advanced-header-pages-controlling]]
==== 控制报头页

通过启用报头页， LPD 将生成出一个 _长报头_， 一整页的大字母， 标着用户， 主机和任务名。 下面是一个例子 (`kelly` 从主机 `rose` 打印了一个叫做 "outline" 的任务)：

[source,shell]
....
      k                   ll       ll
      k                    l        l
      k                    l        l
      k   k     eeee       l        l     y    y
      k  k     e    e      l        l     y    y
      k k      eeeeee      l        l     y    y
      kk k     e           l        l     y    y
      k   k    e    e      l        l     y   yy
      k    k    eeee      lll      lll     yyy y
                                               y
                                          y    y
                                           yyyy

                                   ll
                          t         l        i
                          t         l
       oooo    u    u   ttttt       l       ii     n nnn     eeee
      o    o   u    u     t         l        i     nn   n   e    e
      o    o   u    u     t         l        i     n    n   eeeeee
      o    o   u    u     t         l        i     n    n   e
      o    o   u   uu     t  t      l        i     n    n   e    e
       oooo     uuu u      tt      lll      iii    n    n    eeee

      r rrr     oooo     ssss     eeee
      rr   r   o    o   s    s   e    e
      r        o    o    ss      eeeeee
      r        o    o      ss    e
      r        o    o   s    s   e    e
      r         oooo     ssss     eeee

                                              Job:  outline
                                              Date: Sun Sep 17 11:04:58 1995
....

LPD 会附加一个换页符在这段文本之后， 所以任务会在新的一页上开始 (除非设置了 `sf` (禁止换纸) 在 [.filename]#/etc/printcap# 文件里目标打印机的记录中)。

如果您喜欢， LPD 可以生成一个 _短报头_； 指定 `sb` (短 banner) 在文件 [.filename]#/etc/printcap# 中。 报头页就会看起来像下面这样：

[source,shell]
....
rose:kelly  Job: outline  Date: Sun Sep 17 11:07:51 1995
....

同样是默认的， LPD 也是先打印报头页， 然后才是任务。 要想反过来， 在 [.filename]#/etc/printcap# 中指定 `hl` (最后报头)。

[[printing-advanced-header-pages-accounting]]
==== 为带报头页的任务记帐

使用 LPD 内置的报头页会在进行打印记帐的时候产生一种特殊情况： 报头页肯定是 _免费_ 的。

为什么？

因为输出过滤器是仅有的一个在打印报头页时能进行记帐的外部程序， 但却没有提供给它任何 _用户或者主机_ 的信息或者记帐文件， 所以它无法知道谁应该为打印机的使用付费。 如果仅仅是 "增加一页计数" 给文本过滤器或者其他过滤器 (它们有用户和主机的信息) 是不够的， 因为用户可以用 `lpr -h` 命令跳过报头页。 他还是需要为自己并没有打印的报头页付钱。 基本上， `lpr -h` 是明知用户的首选， 但也不能强制让别人使用它。

让每个过滤器生成自己的报头页 (因此可以为它们计费) 是 _仍然不够的_。 如果用户想要用 `lpr -h` 命令禁止报头页， 它们将仍然印出报头页并且为它们付费。 因为 LPD 不会把 `-h` 这个参数传给任何过滤器。

这样， 您该怎么办呢？

您可以：

* 认可 LPD 的这个问题， 并且免费提供报头页打印。
* 安装一个替代 LPD 的软件， 比如 LPRng。 参考 <<printing-lpd-alternatives,替换标准的后台打印软件>> 来得到更多关于可以替代 LPD 的软件的信息。 
* 写一个 _聪明的_ 输出过滤器。 通常， 输出过滤器不应该去完成除了初始化打印机或者进行一些简单字符转换以外的任何事情。 它适合完成报头页和纯文本任务 (当没有文本 (输入) 过滤器时)。 但是， 如果有文本过滤器为纯文本任务服务， 那么 LPD 将仅为打印报头页启动输出过滤器。 而且， 这个输出过滤器可以理解报头页里 LPD 生成的信息， 然后决定哪位用户和主机应该为报头页付费。 这种方法仅有的问题是输出过滤器仍然不知道应该使用什么记帐文件 (`af` 变量的内容并没有被传递过来)， 但是如果您有一个众所周知的记帐文件， 就可以直接把文件名写进输出过滤器。 为了简化解释报头的步骤， 我们定义 `sh` (短报头) 变量在 [.filename]#/etc/printcap# 文件中。 但这些还是太麻烦了， 而且用户也更喜欢让他们免费打印报头页的慷慨的系统管理员。

[[printing-advanced-header-pages-ps]]
==== 在 PostScript(R) 打印机上打印报头页

像上面描述的那样，LPD 可以生成一个纯文本的报头页来适应多种打印机。 当然， PostScript(R) 不能直接打印纯文本， 所以 LPD 没什么用-或者说大多时候是这样。

一个显而易见的方法来得到报头页就是让每个转换过滤器和文本过滤器都来生成报头页。 这些过滤器应该用用户名和主机的参数来生成一个相对应的报头页。 这种方法的缺点就是用户总是打印出报头页， 无论他们是否用 `lpr -h` 命令来提交的任务。

让我们来深入深入的研究一下这个方法。 下面的脚本输入三个参数 (用户登录名， 主机名， 和任务名) 然后生成一个简单的 PostScript(R) 报头页：

[.programlisting]
....
#!/bin/sh
#
#  make-ps-header - make a PostScript header page on stdout
#  Installed in /usr/local/libexec/make-ps-header
#

#
#  These are PostScript units (72 to the inch).  Modify for A4 or
#  whatever size paper you are using:
#
page_width=612
page_height=792
border=72

#
#  Check arguments
#
if [ $# -ne 3 ]; then
    echo "Usage: `basename $0` <user> <host> <job>" 1>&2
    exit 1
fi

#
#  Save these, mostly for readability in the PostScript, below.
#
user=$1
host=$2
job=$3
date=`date`

#
#  Send the PostScript code to stdout.
#
exec cat <<EOF
%!PS

%
%  Make sure we do not interfere with user's job that will follow
%
save

%
%  Make a thick, unpleasant border around the edge of the paper.
%
$border $border moveto
$page_width $border 2 mul sub 0 rlineto
0 $page_height $border 2 mul sub rlineto
currentscreen 3 -1 roll pop 100 3 1 roll setscreen
$border 2 mul $page_width sub 0 rlineto closepath
0.8 setgray 10 setlinewidth stroke 0 setgray

%
%  Display user's login name, nice and large and prominent
%
/Helvetica-Bold findfont 64 scalefont setfont
$page_width ($user) stringwidth pop sub 2 div $page_height 200 sub moveto
($user) show

%
%  Now show the boring particulars
%
/Helvetica findfont 14 scalefont setfont
/y 200 def
[ (Job:) (Host:) (Date:) ] {
200 y moveto show /y y 18 sub def }
forall

/Helvetica-Bold findfont 14 scalefont setfont
/y 200 def
[ ($job) ($host) ($date) ] {
        270 y moveto show /y y 18 sub def
} forall

%
% That is it
%
restore
showpage
EOF
....

现在， 每个转换过滤器和文本过滤器都能调用这段脚本来生成报头页， 然后打印用户的任务。 下面是我们早些时候在这个文档中提到的 DVI 转换过滤器， 被修改之后来生成一个报头页：

[.programlisting]
....
#!/bin/sh
#
#  psdf - DVI to PostScript printer filter
#  Installed in /usr/local/libexec/psdf
#
#  Invoked by lpd when user runs lpr -d
#

orig_args="$@"

fail() {
    echo "$@" 1>&2
    exit 2
}

while getopts "x:y:n:h:" option; do
    case $option in
        x|y)  ;; # Ignore
        n)    login=$OPTARG ;;
        h)    host=$OPTARG ;;
        *)    echo "LPD started `basename $0` wrong." 1>&2
              exit 2
              ;;
    esac
done

[ "$login" ] || fail "No login name"
[ "$host" ] || fail "No host name"

( /usr/local/libexec/make-ps-header $login $host "DVI File"
  /usr/local/bin/dvips -f ) | eval /usr/local/libexec/lprps $orig_args
....

过滤器是怎样解释参数列表来决定用户名和主机名的。 解释的方法对于其他转换过滤器来说也是一样的。 尽管文本过滤器需要输入的参数有些小的不同， (参见 <<printing-advanced-filters,过滤器是怎样工作的>>)。

像我们以前提到的那样， 上面的配置， 尽管相当简单， 关掉了 "禁止报头页" 的选项 (`-h` 选项) 在 `lpr` 中。 如果用户想要保护树木 (或者是几便士， 如果你对打印报头页收费的话)， 它还不能完成这件事情， 因为每个过滤器都要为每个任务打印一个报头页。

要允许用户对于每个任务都可以关闭报头页， 您需要使用在 <<printing-advanced-header-pages-accounting,为报头页记帐>> 这节中介绍的那种技巧： 写一个输出过滤器来解释 LPD- 生成的报头页并且生成一个 PostScript(R) 的版本。 如果用户用 `lpr -h` 命令提交任务， 那么 LPD 将不会生成报头页， 并且输出过滤器也不会生成报头页。 否则， 输出过滤器将从 LPD 读取文本， 然后发送适当的报头页的 PostScript(R) 编码给打印机。

如果您有的是一台连在串口上的 PostScript(R) 打印机， 您可以使用 `lprps` 里的一个输出过滤器， `psof` ， 它可以完成上述任务。 但注意 `psof` 不对报头页计费。

[[printing-advanced-network-printers]]
=== 网络打印

FreeBSD 支持网络打印： 发送任务给远程打印机。 网络打印通常指两种不同的方式：

* 访问一台连接在远程主机上的打印机。 在一台主机上安装一台常规的串口或并口打印机。 然后， 设置 LPD 来通过网络访问其他主机上的打印机。 具体见 <<printing-advanced-network-rm,安装在远程主机上的打印机>> 这节。
* 访问一台直接连接在网络上的打印机。 打印机另有一个网络接口 (或者替代常规的串口或者并口)。 这样的打印机可能像下面这样工作：

** 它或许可以理解 LPD 的协议， 并且甚至可以接收远程主机发来的任务排进队列。 这样， 它就像一个普通的主机运行着 LPD 一样。 做在 <<printing-advanced-network-rm,安装在远程主机上的打印机>> 里介绍的步骤， 可以设置好这样的打印机。
** 它或许支持网络数据流。 这样， 把打印机 "接" 在一台网络上的主机上， 由这台主机负责安排任务并发送任务到打印机。 参见 <<printing-advanced-network-net-if,带网络数据流接口的打印机>> 这节来得到更多安装这类打印机的建议。

[[printing-advanced-network-rm]]
==== 安装在远程主机上的打印机

LPD 后台打印系统内建了对给其他也运行着 LPD (或者是与 LPD 兼容的) 的主机发送任务的功能。 这个功能使您可以在一台主机上安装打印机， 并让它可以在其他主机上访问。 这个功能同样适用在那些有网络接口并且可以理解 LPD 协议的打印机上。

要开启这种远程打印的功能， 首先在一台主机上安装打印机， 就是 _打印服务器_， 可以使用在 <<printing-simple,简单打印机设置>> 这节中简单设置的方法。 高级的设置可以参考 <<printing-advanced,高级打印机设置>> 这节中你需要的部分。 一定要测试一下打印机， 看看它是不是所有您开启的 LPD 的功能都正常工作。 此外还需要确认 _本地主机_ 允许使用 _远程主机_ 上的 LPD 服务 (参见 <<printing-advanced-restricting-remote,限制远程主打印任务>>)。

如果您正在使用一台带网络接口并与 LPD 兼容的打印机， 那么我们那下面讨论中的 _打印服务器_ 就是打印机本身， 而 _打印机名_ 就是您为打印机配置的名字。 参考随打印机和/或者打印机-网络接口供给的文档。

[TIP]
====

如果您正使用惠普的 Laserjet， 则打印机名 `text` 将自动地为您完成 LF 到 CRLF 的转换， 因而也就不需要 [.filename]#hpif# 脚本了。
====

然后， 在另外一台你想要访问打印机的主机上的 [.filename]#/etc/printcap# 文件中加入它们的记录， 像下面这样：

. 可以随意给这个记录起名字。 简单起见， 您可以给打印服务器使用相同的名字或者别名。
. 保留 `lp` 变量为空， (`:lp=:`)。
. 建立一个后台打印队列目录， 并用 `sd` 变量指明其位置。 LPD 将把任务提交给打印服务器之前， 会把这些任务保存在这里。
. 在 `rm` 变量中放入打印服务器的名字。
. 在 `rp` 中放入打印服务器上打印机的名字。

就是这样。 不需要列出转换过滤器， 页面大小， 或者其他的一些东西在 [.filename]#/etc/printcap# 文件中。

这有一个例子。 主机 `rose` 有两台打印机， `bamboo` 和 `rattan`。 我们要让主机 `orchid` 的用户可以使用这两台打印机。 下面是 [.filename]#/etc/printcap# 文件， 用在主机 `orchid` (详见 <<printing-advanced-header-pages-enabling,开启报头页>>) 上的。 文件中已经有了打印机 `teak` 的记录； 我们在主机 `rose` 上增加了两台打印机：

[.programlisting]
....
#
#  /etc/printcap for host orchid - added (remote) printers on rose
#

#
#  teak is local; it is connected directly to orchid:
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
        :lp=/dev/lpt0:sd=/var/spool/lpd/teak:mx#0:\
        :if=/usr/local/libexec/ifhp:\
        :vf=/usr/local/libexec/vfhp:\
        :of=/usr/local/libexec/ofhp:

#
#  rattan is connected to rose; send jobs for rattan to rose:
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :lp=:rm=rose:rp=rattan:sd=/var/spool/lpd/rattan:

#
#  bamboo is connected to rose as well:
#
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :lp=:rm=rose:rp=bamboo:sd=/var/spool/lpd/bamboo:
....

然后， 我们只需要在主机 `orchid` 上建立一个后台打印队列目录：

[source,shell]
....
# mkdir -p /var/spool/lpd/rattan /var/spool/lpd/bamboo
# chmod 770 /var/spool/lpd/rattan /var/spool/lpd/bamboo
# chown daemon:daemon /var/spool/lpd/rattan /var/spool/lpd/bamboo
....

现在， 主机 `orchid` 上的用户可以打印到 `rattan` 和 `bamboo` 了。 如果， 比如， 一个用户在主机 `orchid` 上输入了：

[source,shell]
....
% lpr -P bamboo -d sushi-review.dvi
....

LPD 系统在主机 `orchid` 上会复制这个任务到后台打印队列目录 [.filename]#/var/spool/lpd/bamboo# 并且记下这是一个 DVI 任务。 当主机 `rose` 上的打印机 `bamboo` 的后台打印队列目录有空间的时， 这两个 LPD 系统将会传输这个文件到主机 `rose` 上。 文件将排在主机 `rose` 的队列中直到最终被打印出来。 它将被从 DVI 转换成 PostScript(R) (因为 `bamboo` 是一台 PostScript(R) 打印机) 在主机 `rose`。

[[printing-advanced-network-net-if]]
==== 带有网络数据流接口的打印机

通常， 当您为打印机购买了一块网卡， 可以得到两个版本： 一个是模拟后台打印 (贵一些的版本)， 或者一个只发送数据给打印机就像在使用串口或者并口一样 (便宜一些的版本)。 这节讲述如何使用这个便宜一些的版本。 要得到贵一些版本的更多信息， 参见前面章节 <<printing-advanced-network-rm,安装在远程主机上的打印机>>。

[.filename]#/etc/printcap# 文件的格式让您指定使用哪个串口或并口， 并且还要指定 (如果您正在使用串口)， 使用多快的波特， 是否使用流量控制， 为制表符延迟， 转换换行， 等等。 但是没有一种方法指定一个连接到一台正在监听 TCP/IP 的或者其他网络接口的打印机。

要发送数据到网络打印机， 就需要开发一个通讯程序， 它可以被文本或者转换过滤器调用。 下面是一些例子： 脚本 `netprint` 将标准输入的所有数据发送到一个连在网络上的打印机。 我们将打印机的名字作为第一个参数， 端口号跟在后面作为第二个参数， 传给 `netprint`。 注意它只支持单向通讯 (FreeBSD 到打印机)； 很多网络打印机支持双向通讯， 并且这是您可能利用到的 (得到打印机状态， 进行打印记帐， 等等的时候。)。

[.programlisting]
....
#!/usr/bin/perl
#
#  netprint - Text filter for printer attached to network
#  Installed in /usr/local/libexec/netprint
#
$#ARGV eq 1 || die "Usage: $0 <printer-hostname> <port-number>";

$printer_host = $ARGV[0];
$printer_port = $ARGV[1];

require 'sys/socket.ph';

($ignore, $ignore, $protocol) = getprotobyname('tcp');
($ignore, $ignore, $ignore, $ignore, $address)
    = gethostbyname($printer_host);

$sockaddr = pack('S n a4 x8', &AF_INET, $printer_port, $address);

socket(PRINTER, &PF_INET, &SOCK_STREAM, $protocol)
    || die "Can't create TCP/IP stream socket: $!";
connect(PRINTER, $sockaddr) || die "Can't contact $printer_host: $!";
while (<STDIN>) { print PRINTER; }
exit 0;
....

然后我们就可以在多种过滤器里使用这个脚本了。 加入我们有一台 Diablo 750-N 行式打印机联在网络上。 打印机在 5100 端口上接收要打印的数据。 打印机的主机名是 `scrivener`。 这里是为这个打印机写的文本过滤器：

[.programlisting]
....
#!/bin/sh
#
#  diablo-if-net - Text filter for Diablo printer `scrivener' listening
#  on port 5100.   Installed in /usr/local/libexec/diablo-if-net
#
exec /usr/libexec/lpr/lpf "$@" | /usr/local/libexec/netprint scrivener 5100
....

[[printing-advanced-restricting]]
=== 限制打印机的使用

这节将讲述关于限制打印机使用的问题。 LPD 系统让您可以控制谁可以访问打印机， 无论本地或是远程的， 是否他们可以打印机多份副本， 任务可以有多大， 以及打印队列的尺寸等。

[[printing-advanced-restricting-copies]]
==== 限制多份副本

LPD 系统能够简化用户在打印多份副本时的工作。 用户可以用 `lpr -#5` (举例) 来提交打印任务， 则会将任务中每个文件都打印五份副本。 这是不是一件很棒的事情呢。

如果您感觉多份副本会对打印机造成不必要的磨损和损耗， 您可以屏蔽掉 man:lpr[1] 的 `-\#` 选项， 这可以通过在 [.filename]##/etc/printcap## 文件中增加 `sc` 变量来完成。 当用户用 `-#` 选项提交任务时， 他们将看到：

[source,shell]
....
lpr: multiple copies are not allowed
....

注意当为一台远程打印机进行设置时 (参见 <<printing-advanced-network-rm,安装在远程主机上的打印机>> 这一节) 您还需要同时在远程主机的 [.filename]#/etc/printcap# 文件中 增加``sc`` 变量， 否则用户还是可以从其他主机上提交使用多份副本的任务。

下面是一个例子。 这个是 [.filename]#/etc/printcap# 文件在主机 `rose` 上。 打印机 `rattan` 非常轻闲， 所以我们将允许多份副本， 但是激光打印机 `bamboo` 则有些忙， 所以我们禁止多份副本， 通过增加 `sc` 变量：

[.programlisting]
....
#
#  /etc/printcap for host rose - restrict multiple copies on bamboo
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:sc:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:rw:\
        :if=/usr/local/libexec/psif:\
        :df=/usr/local/libexec/psdf:
....

现在， 我们还需要增机 `sc` 变量在主机 `orchid` 的 [.filename]#/etc/printcap# 文件中 (顺便我们也禁止打印机 `teak` 多份打印) ：

[.programlisting]
....
#
#  /etc/printcap for host orchid - no multiple copies for local
#  printer teak or remote printer bamboo
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
        :lp=/dev/lpt0:sd=/var/spool/lpd/teak:mx#0:sc:\
        :if=/usr/local/libexec/ifhp:\
        :vf=/usr/local/libexec/vfhp:\
        :of=/usr/local/libexec/ofhp:

rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :lp=:rm=rose:rp=rattan:sd=/var/spool/lpd/rattan:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :lp=:rm=rose:rp=bamboo:sd=/var/spool/lpd/bamboo:sc:
....

通过使用 `sc` 变量， 我们阻止了 `lpr -#` 命令的使用， 但仍然没有禁止用户多次运行 man:lpr[1] ， 或者多次提交任务中同样的文件， 像下面这样：

[source,shell]
....
% lpr forsale.sign forsale.sign forsale.sign forsale.sign forsale.sign
....

这里有很多种方法可以阻止这种行为 (包括忽略它)， 并且是免费的。

[[printing-advanced-restricting-access]]
==== 限制对打印机的访问

您可以控制谁可以打印到哪台打印机通过 UNIX(R) 的组机制和文件 [.filename]#/etc/printcap# 中的 `rg` 变量。 只要把可以访问打印机的用户放进适当的组中， 然后在 `rg` 变量中写上组的名字。

如果这组以外的用户 (包括 `root`) 试图打印到被限制的打印机，将会得到这样的提示：

[source,shell]
....
lpr: Not a member of the restricted group
....

像使用 `sc` (禁止多份副本) 变量一样， 您需要指定 `rg` 在远程同样对打印机有访问限制的主机上， 如果您感觉合适的话 (参考 <<printing-advanced-network-rm,安装在远程主机上的打印机>> 这一节)。

比如， 我们将让任何人都可以访问打印机 `rattan`， 但只有在 `artists` 组中的人可以使用打印机 `bamboo`。 这里是类似的主机 `rose` 上的 [.filename]#/etc/printcap# 文件：

[.programlisting]
....
#
#  /etc/printcap for host rose - restricted group for bamboo
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:sc:rg=artists:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:rw:\
        :if=/usr/local/libexec/psif:\
        :df=/usr/local/libexec/psdf:
....

Let us leave the other example [.filename]#/etc/printcap# file (for the host `orchid`) alone. Of course, anyone on `orchid` can print to `bamboo`. It might be the case that we only allow certain logins on `orchid` anyway, and want them to have access to the printer. Or not.

[NOTE]
====
这里每台仅能有一个限制的组。
====

[[printing-advanced-restricting-sizes]]
==== 控制提交的任务大小

如果您有很多用户访问打印机， 可能需要对用户可以提交的文件尺寸设置一个上限。 毕竟， 文件系统中后台打印队列目录的空间是有限的， 您需要保证这里有空间来存放其他用户的任务。

LPD 允许通过使用 `mx` 变量来限制任务中文件的最大字节数， 方法是指定单位为块的 `BUFSIZ` 数， 每块表示 1024 字节。 如果在这个变量的值是 0， 则表示不进行限制； 不过， 如果不指定 `mx` 变量的话， 则会使用默认值 1000 块。

[NOTE]
====
这个限制是对于任务中 _文件_ 的， 而 _不是_ 任务总共的大小。
====

LPD 不会拒绝比限制大小大的文件。 但它是将限制大小以内的部分排入队列， 并且打印出来的只有这些。 剩下的部分将被丢弃。 这个行为是否正确还需讨论。

让我们来为例子打印机 `rattan` 和 `bamboo` 增加限制。 由于那些 `artists` 的 PostScript(R) 文件可能会很大， 我们将限制大小为 5 兆字节。 我们将不对纯文本行式打印机做限制：

[.programlisting]
....
#
#  /etc/printcap for host rose
#

#
#  No limit on job size:
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:mx#0:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:

#
#  Limit of five megabytes:
#
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:sc:rg=artists:mx#5000:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:rw:\
        :if=/usr/local/libexec/psif:\
        :df=/usr/local/libexec/psdf:
....

同样， 限制只对本地用户起作用。 如果设置了允许远程用户使用您的打印机， 远程用户将不会受到这些限制。 您也需要指定 `mx` 变量在远程主机的 [.filename]#/etc/printcap# 文件中。 参见 <<printing-advanced-network-rm,安装在远程主机上的打印机>> 这一节来得到更多有关远程打印的信息。

除此之外， 还有另一种限制远程任务大小的方法； 参见 <<printing-advanced-restricting-remote,限制远程主机打印任务>>。

[[printing-advanced-restricting-remote]]
==== 限制远程主机打印任务

LPD 后台打印系统提供了多种方法来限制从远程主机提交的任务：

主机限制::
您可以控制本地 LPD 接收哪台远程主机发来的请求， 通过 [.filename]#/etc/hosts.equiv# 文件和 [.filename]#/etc/hosts.lpd# 文件。 LPD 查看是否到来的任务请求来自被这两个文件中列出的主机。 如果没有， LPD 会拒绝这个请求。
+
这些文件的格式非常简单： 每行一个主机名。 注意 [.filename]#/etc/hosts.equiv# 文件也被 man:ruserok[3] 协议使用， 并影响着 man:rsh[1] and man:rcp[1] 等程序， 所以要小心。
+
举个例子， 下面是 [.filename]#/etc/hosts.lpd# 文件在主机 `rose` 上：
+
[.programlisting]
....
orchid
violet
madrigal.fishbaum.de
....
+
意思是主机 `rose` 将接收来自 `orchid`， `violet`， 和 `madrigal.fishbaum.de` 的请求。 如果任何其他的主机试图访问主机 `rose` 的 LPD， 任务将被拒绝。

大小限制::
您可以控制后台打印队列目录需要保留多少空间。 建立一个叫做 [.filename]#minfree# 的文件在后台打印队列目录下为本地打印机。 在这个文件中插入一个数字来代表多少磁盘块数 (512 字节) 的剩余空间来接收远程任务。
+
这让您可以保证远程用户不会填满您的文件系统。 您也可以用它来给本地用户一个优先： 他们可以在磁盘剩余空间低于 [.filename]#minfree# 文件中的指定值后仍然可以提交任务。
+
比如， 让我们增加一个 [.filename]#minfree# 文件为打印机 `bamboo`。 我们检查 [.filename]#/etc/printcap# 文件来找到这个打印机的后台打印队列目录； 这里是打印机 `bamboo` 的记录：
+
[.programlisting]
....
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
        :sh:sd=/var/spool/lpd/bamboo:sc:rg=artists:mx#5000:\
        :lp=/dev/ttyu5:ms#-parenb cs8 clocal crtscts:rw:mx#5000:\
        :if=/usr/local/libexec/psif:\
        :df=/usr/local/libexec/psdf:
....
+
后台打印队列目录在 `sd` 变量中给出。 我们设置 3 兆字节 (6144 磁盘块) 为文件系统上必须存在的总共剩余空间， 让 LPD 可以接受远程任务：
+
[source,shell]
....
# echo 6144 > /var/spool/lpd/bamboo/minfree
....

用户限制::
您可以控制哪些远程用户可以打印到本地打印机， 通过指定 `rs` 变量在 [.filename]#/etc/printcap# 文件中。 当 `rs` 出现在一个本地打印机的记录中时， LPD 将接收来自远程主机 _并_ 在本地有同样登录名的用户提交的任务。 否则， LPD 会拒绝这个任务。
+
这个功能在一个 (比如) 有许多部门共享一个网络的环境中特别有用， 并且有些用户可以越过部门的边界。 通过为他们在您的系统上建立帐号， 他们可以他们自己的部门的系统里使用您的打印机。 如果 _只_ 允许他们您的打印机， 而不是您的计算机资源， 您可以给他们 "象征" 帐户， 不带主目录并且设置一个没用的 shell ， 比如 [.filename]#/usr/bin/false#。

[[printing-advanced-acct]]
=== 对打印机使用记帐

当然， 你需要对打印付费。 为什么不？ 纸张和墨水都需要花钱的。 并且这里还有维护的费用 - 打印机是由很多部件组装成的， 并且零件会坏掉。 您可以检查您的打印机， 使用形式， 和维护费用来得出每页 (或者每尺， 每米， 或者每什么) 的费用。 现在， 您怎样启动打印记帐呢？

好了， 坏消息是 LPD 后台打印系统在这个部分没有提供很多帮助。 记帐是一个对使用的打印机的种类， 打印的格式， 和 _您的_ 在对打印机的使用计费的需求依赖性很高的。

要实现记帐， 您必须更改打印机的文本过滤器 (对纯文本任务记费) 和转换过滤器 (对其他格式的文件计费)， 要统计页数或者查询打印了多少页的话。 您不可以通过使用简单的输出过滤器来逃脱计费， 因为它不能进行记帐。 参见 <<printing-advanced-filter-intro,过滤器>> 这节。

通常， 有两种方法来进行记帐：

* _定期记帐_ 是更常用的方法， 可能因为它更简单。 无论合适何人打印一个任务， 过滤器都将记录用户名， 主机名， 和打印的页数到一个记帐文件。 每个月， 学期， 年， 或者任何您想设定的时间段， 收集这些不同打印机上的记帐文件， 按用户对打印的页数进行结算， 并对使用进行付费。 然后删掉所有记录文件， 开始一个新的计费周期。
* _实时记帐_ 不太常用， 可能因为它比较难。 这种方法让过滤器对用户的打印进行实时的记帐。 像磁盘配额， 记帐是实时的。 您可以组织用户打印当他们的帐户超额的时候， 并且可能提供一种方法让用户检查并调整他们的 "打印配额。" 但这个方法需要一些数据库代码来跟踪用户和他们的配额。

LPD 后台打印系统对两种方法都支持且很简单： 所以您需要提供过滤器 (大多数时候)， 还要提供记帐代码。 但这好的方面是： 您可以有非常灵活的记帐方法。 比如， 您可以选择使用阶段记帐还是实时记帐。 您可以选择记录哪些信息： 用户名， 主机名， 任务类型， 打印页数， 使用了多少平方尺的纸， 任务打印了多长时间， 等等。 您可以通过修改过滤器来存储这些信息。

==== 快速并且混乱的打印记帐

FreeBSD 包含两个可以让您立刻可以建立起简单的阶段记帐的程序。 它们是文本过滤器 `lpf`， 在 <<printing-advanced-lpf,lpf： 一个文本过滤器>> 这节中描述， 和 man:pac[8]， 一个收集并统计打印机记帐文件中记录的程序。

像在前面章节提到的过滤器一样 (<<printing-advanced-filters,过滤器>>)， LPD 启动文本或者转换过滤器并在过滤器命令行里带上记帐文件的名字。 过滤器可以使用这个参数知道该往哪写记帐记录。 这个文件的名字来自于 `af` 变量在 [.filename]#/etc/printcap# 文件里， 并且如果没有指定绝对路径， 则默认是相对于后台打印队列目录的。

LPD 启动 `lpf` 带着页宽和页长的参数 (通过 `pw` 和 `pl` 变量)。 `lpf` 使用这些参数来判定将使用多少张纸。 在文件发送到打印机之后， 它就会在记帐文件中写入记录。 记录像下面这个样子：

[.programlisting]
....
2.00 rose:andy
3.00 rose:kelly
3.00 orchid:mary
5.00 orchid:mary
2.00 orchid:zhang
....

您应该让每个打印机都使用一个独立的记帐文件， 像 `lpf` 就没有内建文件锁逻辑， 这样两个 `lpf` 可能会发生彼此记录混合的情况， 如果它们同时要在同一个文件写入内容的时候。 一个最简单的保证每个打印机都使用一个独立的记帐文件的方法就是将 `af=acct` 写在 [.filename]#/etc/printcap# 文件中。 然后， 每个打印机的记帐文件都会在这台打印机的后台打印队列目录中， 文件的名字叫做 [.filename]#acct#。

当您准备对用户的打印进行收费时， 运行 man:pac[8] 程序。 只要转换到要收集信息的这台打印机的后台打印队列目录， 然后输入 `pac`。 您将会得到一个美元计费的摘要像下面这样：

[source,shell]
....
  Login               pages/feet   runs    price
orchid:kelly                5.00    1   $  0.10
orchid:mary                31.00    3   $  0.62
orchid:zhang                9.00    1   $  0.18
rose:andy                   2.00    1   $  0.04
rose:kelly                177.00  104   $  3.54
rose:mary                  87.00   32   $  1.74
rose:root                  26.00   12   $  0.52

total                     337.00  154   $  6.74
....

这些是 man:pac[8] 需要的参数：

`-P _打印机_`::
哪台 _打印机_ 要结帐。 这个选项仅在用 `af` 变量在 [.filename]#/etc/printcap# 文件中指定了绝对路径的情况下起作用。

`-c`::
以金额来排序输出来代替以用户名字字母排序。

`-m`::
忽略记帐文件中的主机名。 带上这个选项， 用户 `smith` 在主机 `alpha` 上与同样的用户 `smith` 在主机 `gamma` 上一样。 不带这个选项的话， 他们则是不同的用户。

`-p _单价_`::
使用 _price_ 作为每页或每尺美元的单价来替代 `pc` 变量指定的单价在 [.filename]#/etc/printcap# 文件中， 或者两分 (默认)。 _price_ 可以用一个浮点数来指定。

`-r`::
反向排序。

`-s`::
建立一个记帐摘要文件， 并且截短记帐文件。

_名字 ..._::
只打印指定 _名字_ 用户的记帐信息。

在 man:pac[8] 默认产生的摘要中， 可以看到在不同主机上的每个用户打印了多少页。 如果在您这里， 主机不考虑 (因为用户可以使用任何主机)， 运行 `pac -m`， 来得到下面的摘要：

[source,shell]
....
  Login               pages/feet   runs    price
andy                        2.00    1   $  0.04
kelly                     182.00  105   $  3.64
mary                      118.00   35   $  2.36
root                       26.00   12   $  0.52
zhang                       9.00    1   $  0.18

total                     337.00  154   $  6.74
....

要以美元计算应付钱数， man:pac[8] 指定 `pc` 变量在 [.filename]#/etc/printcap# 文件中 (默认是 200， 或者 2 分每页). 这个参数的单位是百分之一分， 在这个变量中指定每页或者每尺的价格。 您可以覆盖这个值当运行 man:pac[8] 带着参数 `-p` 的时候。 参数 `-p` 的单位是美元， 而不是百分之一分。 例如，

[source,shell]
....
# pac -p1.50
....

设定每页的价格是 1 美元 5 美分。 您可以通过这个选项来达到目标利润。

最终， 运行 `pac -s` 将存储这些信息在一个记帐文件里， 文件名和打印机帐户的名字相同， 但是带着 `_sum` 的后缀。 然后截短记帐文件。 当您再次运行 man:pac[8] 的时候， 它再次读取记帐文件来得到初始的总计， 然后在记帐文件中增加信息。

==== 怎样对打印的页数进行计数？

为了进行远程的精确记帐， 需要判断一个任务将会消耗多少张纸。 这是打印记帐问题的关键。

对于纯文本任务， 这个问题不是太难解决： 对任务中的行数进行计数然后与打印机支持的每页行数进行比较。 别忘了也对添印的行， 或者很长的逻辑上的一行但在打印机上会折成两行的这类进行记帐。

文本过滤器 `lpf` (在 <<printing-advanced-lpf,lpf：一个文本过滤器>> 这节中介绍) 会在记帐时考虑这些问题。 如果正在编写一个可以进行记帐的文本过滤器， 您可能需要查看 `lpf` 的源代码。

怎样处理其他格式的文件？

好， 对于 DVI- 到 -LaserJet 或者 DVI- 到 -PostScript(R) 转换， 可以让您的过滤器输出诊断信息， 关于 `dvilj` 或者 `dvips` 命令， 并且看到多少页被转换了。 您也许可以对于其他类型的文件和转换程序进行类似操作。

但是这些方法的弱点就是事实上打印机并不是打印了所有的页。 比如， 卡纸， 缺墨， 或者炸掉了 - 但用户还是要为没有打印的部分付钱。

您该怎样做？

只有一条 _肯定_ 的方法来进行 _精确_ 的记帐。 购买一台可以告诉您它使用了多少纸的打印机， 并且将它连接到串口或者网络上。 几乎所有 PostScript(R) 打印机都支持这个小功能。 其他制造厂或其他型号也可以有这个功能 (比如 Imagen 激光网络打印机)。 为这些打印机更改过滤器使它在打印完每个任务之后接收纸张用量， 并 _仅_ 基于这个值进行记帐。 不需要计算行数， 也不需要容易出错的文件检查。

当然， 您也总是可以大方的使打印免费。

[[printing-using]]
== 使用打印机

这节将讲述如何使用在 FreeBSD 下设置好的打印机。 下面是一个用户级命令的总览：

man:lpr[1]::
打印任务

man:lpq[1]::
检查打印队列

man:lprm[1]::
从打印机的队列中移除任务

还有一个管理命令， man:lpc[8]， 在 <<printing-lpc,管理打印机>> 一节中有所介绍， 它可以用于控制打印机及其队列。

man:lpr[1], man:lprm[1], and man:lpq[1] 这三个命令都接受 `-P _printer-name_` 选项来指定对哪个打印机 / 队列进行操作， 在 [.filename]#/etc/printcap# 文件中列出的打印机。 这允许您提交， 删除， 并检查任务在多个打印机上。 如果您不使用 `-P` 选项， 那么这些命令会使用在 环境变量 `PRINTER` 中指定的打印机。 最终， 如果您也没有 `PRINTER` 这个环境变量， 这些命令的默认值是叫做 `lp` 的这台打印机。

从此以后， 术语 _默认打印机_ 就是指 `PRINTER` 环境变量中指定的这台， 或者叫做 `lp` 的这一台当没有环境变量 `PRINTER` 的时候。

[[printing-lpr]]
=== 打印任务

要打印文件， 输入：

[source,shell]
....
% lpr filename ...
....

这个命令会打印所有列出的文件到默认打印机。 如果没有列出文件， man:lpr[1] 会从标准输入读取打印数据。 比如， 这个命令打印一些重要的系统文件：

[source,shell]
....
% lpr /etc/host.conf /etc/hosts.equiv
....

要选择一个指定的打印机， 输入：

[source,shell]
....
% lpr -P printer-name filename ...
....

这个例子打印一个当前目录的长长的列表到叫做 `rattan` 的这台打印机：

[source,shell]
....
% ls -l | lpr -P rattan
....

因为没有为 man:lpr[1] 命令列出文件， `lpr` 从标准输入读入数据， 在这里是 `ls -l` 命令的输出。

man:lpr[1] 命令同样可以接受多种控制格式的选项， 应用文件转换， 生成多份副本， 等等。 要得到更多信息， 参考 <<printing-lpr-options,打印选项>> 这节。

[[printing-lpq]]
=== 检查任务

当使用 man:lpr[1] 进行打印时， 您希望打印的所有数据被放在一起打包成了一个 "打印任务"， 它被发送到 LPD 后台打印系统。 每台打印机都有一个任务队列， 并且您的任务在队列中等待其他用户的其他任务打印。 打印机按照先来先印的规则打印这些任务。

要显示默认打印机的队列， 输入 man:lpq[1]。 要指定打印机， 使用 `-P` 选项。 例如， 命令 

[source,shell]
....
% lpq -P bamboo
....
会显示打印机 `bamboo` 的队列。 下面是命令 `lpq` 输出的一个例子：

[source,shell]
....
bamboo is ready and printing
Rank   Owner    Job  Files                              Total Size
active kelly    9    /etc/host.conf, /etc/hosts.equiv   88 bytes
2nd    kelly    10   (standard input)                   1635 bytes
3rd    mary     11   ...                                78519 bytes
....

这里显示了队列中有三个任务在 `bamboo` 中。 第一个任务， 用户 kelly 提交的， 标识 "任务编号" 9。 每个要打印的任务都会获得一个不同的任务编号。 大多时候可以忽略这个任务编号， 但在您需要取消任务时会用到这个号码； 参考 <<printing-lprm,移除任务>> 这节得到更多信息。

编号为 9 的任务包含了两个文件； 在 man:lpr[1] 命令行中指定的多个文件被看作是一个单个的任务。 它是当前激活的任务 (注意这个词 `激活` 在 "Rank" 这列下面)， 意思是打印机当前正在打印那个任务。 第二个任务包含了标准输入传给 man:lpr[1] 命令的数据。 第三个任务来自用户 `mary`; ， 它是一个比较大的任务。 她要打印的文件的路径名太长了， 所以 man:lpq[1] 命令只显示了三个点。

man:lpq[1] 输出的头一行也很有用： 它告诉我们打印机正在做什么 (或者至少是 LPD 认为打印机应该正在做的)。

man:lpq[1] 命令同样支持 `-l` 选项来生成一个详细的长列表。 下面是一个 `lpq -l` 命令的例子：

[source,shell]
....
waiting for bamboo to become ready (offline ?)
kelly: 1st				 [job 009rose]
       /etc/host.conf                    73 bytes
       /etc/hosts.equiv                  15 bytes

kelly: 2nd				 [job 010rose]
       (standard input)                  1635 bytes

mary: 3rd                                [job 011rose]
      /home/orchid/mary/research/venus/alpha-regio/mapping 78519 bytes
....

[[printing-lprm]]
=== 移除任务

如果您对一个打印任务改变了主意， 可以用 man:lprm[1] 将任务从队列中删除。 通常， 您甚至可以用 man:lprm[1] 命令来移除一个当前激活的任务， 但是任务的一部分或者所有还是可能打印出来。

要从默认打印机中移除一个任务， 首先使用 man:lpq[1] 找到任务编号。 然后输入：

[source,shell]
....
% lprm job-number
....

要从指定打印机中删除任务， 增加 `-P` 选项。 下面的命令会删除编号为 10 的任务从 `bamboo` 这台打印机：

[source,shell]
....
% lprm -P bamboo 10
....

man:lprm[1] 命令有一些快捷方式：

lprm -::
删除所有属于您的任务 (默认打印机的)。

lprm _user_::
删除所有属于用户 _user_ 的任务 (默认打印机的)。 超级用户可以删除用户的任务； 您只可以删除自己的任务。

lprm::
命令行中不带任务编号， 任务名， 或者 `-` 选项， man:lprm[1] 会删除默认打印机上当前激活的任务， 如果它属于你。 超级用户可以删除任务激活的任务。

使用参数 `-P` 和上面的快捷方式来用指定打印机替代默认打印机。 例如， 下面的命令会删除当前用户在打印机 `rattan` 队列中的所有任务：

[source,shell]
....
% lprm -P rattan -
....

[NOTE]
====
如果您正工作在一个网络环境中， man:lprm[1] 将只允许在提交任务的主机上删除任务， 甚至是同一台打印机也可以在其他主机上使用时。 下面的命令证明了这个：

[source,shell]
....
% lpr -P rattan myfile
% rlogin orchid
% lpq -P rattan
Rank   Owner	  Job  Files                          Total Size
active seeyan	  12	...                           49123 bytes
2nd    kelly      13   myfile                         12 bytes

% lprm -P rattan 13
rose: Permission denied

% logout
% lprm -P rattan 13
dfA013rose dequeued
cfA013rose dequeued
....

====

[[printing-lpr-options]]
=== 超越纯文本：打印选项

man:lpr[1] 支持许多控制文本格式的参数， 转换图形和其他格式文件， 生成多份副本， 处理任务， 等等。 这一节将描述这些选项。

[[printing-lpr-options-format]]
==== 格式与转换选项

下面的 man:lpr[1] 参数控制任务中文件的格式。 使用这些参数， 如果任务不含纯文本， 或者您想让纯文本通过 man:pr[1] 格式化。

例如， 下面的命令打印一个 DVI 文件 (来自 TeX 排版系统) 文件名为 [.filename]#fish-report.dvi# 到打印 `bamboo`：

[source,shell]
....
% lpr -P bamboo -d fish-report.dvi
....

这些选项应用到任务中的每个文件， 所以您不能混合 (说) DVI 和 ditroff 文件在同一个任务中。 替代的方法是， 用独立的任务提交这些文件， 使用不同的转换选项给不同的任务。

[NOTE]
====
所有这些选项除了 `-p` 和 `-T` 都需要转换过滤器安装给目标打印机。 例如， `-d` 选项需要 DVI 转换过滤器。 参考 <<printing-advanced-convfilters,转换过滤器>> 这节得到更多细节。
====

`-c`::
打印 cifplot 文件。

`-d`::
打印 DVI 文件。

`-f`::
打印 FORTRAN 文本文件。

`-g`::
打印 plot 数据。

`-i _number_`::
缩进 _number_ 列； 如果没有指定 _number_， 则缩进 8 列。 这个选项仅可以工作在某些过滤器上。
+

[NOTE]
====
不要在选项 `-i` 和数字之间加入空格。
====

`-l`::
打印文字数据， 包括控制字符。

`-n`::
打印 ditroff (无设备依赖 troff) 数据。

-p::
打印之前用 man:pr[1] 格式化纯文本。 参考 man:pr[1] 得到更多信息。

`-T _title_`::
使用 _title_ 在 man:pr[1] 上来替代文件名。 这个选项仅在使用 `-p` 选项时起作用。

`-t`::
打印 troff 数据。

`-v`::
打印 raster 数据。

下面是一个例子： 这个命令打印了一个很好的 man:ls[1] 联机手册到默认打印机：

[source,shell]
....
% zcat /usr/shared/man/man1/ls.1.gz | troff -t -man | lpr -t
....

man:zcat[1] 命令解压缩 man:ls[1] 的手册并且将内容传给 man:troff[1] 命令， 它将格式化这些内容并且生成 GNU troff 输出给 man:lpr[1] ， 它提交任务到 LPD 后台打印。 因为使用了 `-t` 选项为 man:lpr[1] ， 后台打印将会转换 GNU troff 输出到默认打印机可以理解的格式当任务被打印时。

[[printing-lpr-options-job-handling]]
==== 任务处理选项

下面的 man:lpr[1] 选项告诉 LPD 对任务特殊处理：

-# _copies_::
生成 _copies_ 个副本给任务中的每个文件， 替代每个文件一份副本。 管理员可以禁止这个选项来减少打印机的浪费和鼓励复印机的使用。 参考 <<printing-advanced-restricting-copies,限制多份副本>>。
+
这个例子打印三份副本的文件 [.filename]#parser.c# 跟着三份副本的文件 [.filename]#parser.h# 到默认打印机：
+
[source,shell]
....
% lpr -#3 parser.c parser.h
....

-m::
打印完成后发信。 使用这个选项， LPD 系统将会发送邮件到您的帐户， 当它完成了处理您的任务后。 在信中， 它将会告诉您任务是否成功完成或者出现了错误， 并且 (通常) 指明是什么错误。

-s::
不要复制文件到后台打印队列目录， 要使用符号连接。
+
如果您正在打印一个很大的任务， 您可能需要这个选项。 它节省后台打印队列目录的空间 (您的任务可能使后台打印队列目录所在的文件系统剩余空间超出)。 它同样也节省了时间， 因为 LPD 将不会副本任务的每个字节到后台打印队列目录。
+
这也有一个缺点： 因为 LPD 将直接指向源文件， 您不能修改或者删除它们直到它们被打印出来。
+
[NOTE]
====
如果您打印到一台远程打印机， LPD 将最终将文件从本地主机副本到远程主机上， 所以选项 `-s` 只能节省本地后台打印队列目录的空间， 而不是远程的。 虽然如此， 但它还是很有用。
====

-r::
移除任务中的文件在它们被复制到后台打印队列目录之后， 或者在用 `-s` 选项打印它们之后。 谨慎使用这个选项！

[[printing-lpr-options-misc]]
==== 报头页选项

这些 man:lpr[1] 的选项调整了通常出现在任务报头页上的文本。 如果报头页被跳过了在目标打印机上， 这些选项将不会起作用。 参考 <<printing-advanced-header-pages,报头页>> 得到更多关于设置报头页的信息。

-C _text_::
替换报头页上的主机名为 _text_。 主机名通常都是提交任务的主机名称。

-J _text_::
替换报头页上的任务名为 _text_。 任务名通常是任务中头一个文件的名字， 或者 [.filename]#stdin# 如果您正在打印标准输入。

-h::
不打印任何报头页。
+
[NOTE]
====
在某些地点， 这个选项可能无效， 与报头页的产生方法有关。 参考 <<printing-advanced-header-pages,报头页>> 得到详细信息。
====

[[printing-lpc]]
=== 管理打印机

作为一个打印机的管理者， 您必须要安装， 设置， 并且测试它们。 使用 man:lpc[8] 命令， 您可以与打印机以更多的方式交流。 用 man:lpc[8] ， 您可以

* 启动或停止打印机
* 启用或禁止它们的队列
* 重新安排每个队列中的任务。

首先， 一个关于术语的解释： 如果一个打印机被 _停止_ 了， 它将不会打印它队列中的任何东西。 但用户还是可以提交任务， 它们会在队列中等待直到打印机被 _启动_ 或者队列被清空。

如果一个队列被 _禁止_， 没有用户 (除了 `root`) 可以提交任务到打印机。 一个 _启用_ 的队列允许任务被提交。 一个打印机可以被 _启动_ 但它的队列被禁止， 在这种情况下打印机将打印队列中的任务， 直到队列为空。

通常， 您必须有 `root` 权限来使用 man:lpc[8] 命令。 普通用户可以使用 man:lpc[8] 命令来获得打印机状态并且重启一台挂了的打印机。

这里是一个关于 man:lpc[8] 命令的摘要。 大部分命令带着一个 _printer-name_ 参数来知道要对哪台打印机操作。 您可以用 `all` 填在 _printer-name_ 的位置来代表所有在 [.filename]#/etc/printcap# 文件中列出的打印机。

`abort printer-name`::
取消当前任务并停止打印机。 用户仍然可以提交任务， 如果队列还是启用的。

`clean printer-name`::
从打印机的后台打印队列目录移除旧的文件。 有时， 组成任务的文件没有被 LPD 正确的删除， 特别是在打印中出现错误或者管理活动比较多的时候。 这个命令查找不属于后台打印队列目录的文件并删除它们。

`disable printer-name`::
禁止新任务入队。 如果打印机正在工作， 它将会继续打印队列中剩余的任务。 超级用户 (`root`) 总是可以提交任务， 甚至提交到一个禁止的队列。
+
这个命令在测试一台新打印机或者安装过滤器时非常有用： 禁止队列并提交以 `root` 提交任务。 其他用户将不能提交任务直到您完成了测试并用命令 `enable` 重新启用了队列的时候。

`down printer-name message`::
打印机下线。 等于 `disable` 命令后跟一个 `stop` 命令。 _message_ 将作为打印机状态， 当用户使用 man:lpq[1] 或者 `lpc status` 命令查看打印机队列状态的时候显示出来。

`enable printer-name`::
为打印机开启队列。 用户可以提交任务到打印机但是在打印机启动之前不会打印出任何东西。

`help command-name`::
打印关于 _command-name_ 命令的帮助。 不带 _command-name_， 则打印可用命令的摘要。

`restart printer-name`::
启动打印机。 普通用户可以使用这个命令， 当一些特别的环境导致 LPD 锁死时， 但他们不能启用一台使用 `stop` 或者 `down` 命令停用的打印机。 `restart` 命令等同于 `abort` 后跟着一个 `start`。

`start printer-name`::
启用打印机。 打印机将开始打印队列中的任务。

`stop printer-name`::
停止打印机。 打印机将完成当前任务并且将不再打印队列中的任务任务。 尽管打印机被停用， 但用户仍然可以提交任务到一个开启的队列。

`topq printer-name job-or-username`::
重新以 _printer-name_ 安排队列， 通过将列出的 _job_ 编号或者指定的所属 _username_ 的任务放在队列的最前面。 对于这个命令， 您不可以使用 `all` 当作 _printer-name_。

`up printer-name`::
打印机上线； 相对于 `down` 命令。 等同于 `start` 后跟着一个 `enable` 命令。

man:lpc[8] 的命令行接受上面的命令。 如果您不输入任何命令， man:lpc[8] 则进入一个交互模式， 在这里您可以输入命令直到输入 `exit`， `quit`， 或者文件结束符。

[[printing-lpd-alternatives]]
== 替换标准后台打印

如果您已经通读过了这个手册， 那么到现在您应该已经了解了关于 FreeBSD 包含的后台打印系统 LPD 的一切。 您可能发现了它很多的缺点， 它们很自然的让您提出这样的问题： "这里还有什么后台打印系统吗 (并且可以工作在 FreeBSD 上) ？"

LPRng::
LPRng， 它的意思是 "LPR： 下一代"， 是一个完全重写的 PLP。 Patrick Powell 和 Justin Mason (PLP 维护的主要负责人) 合作完成了 LPRng。 LPRng 的主站是 http://www.lprng.org/[http://www.lprng.org/]。

CUPS::
CUPS， 通用 UNIX 打印系统， 提供了一个轻便的打印层给 UNIX(R)-基础的操作系统。 它是由 Easy Software Products 开发的， 并且成为了 UNIX(R) 供应商和用户的标准打印解决方案。
+
CUPS 使用 Internet 打印协议 (IPP) 作为管理打印任务和队列的基础。 行式打印机守护程序 (LPD) 服务器消息块 (SMB)， 和 AppSocket (a.k.a. JetDirect) 协议的部分功能也被支持。 CUPS 增加了基于浏览网络打印机和 PostScript 打印机描述 (PPD) 的打印选项来支持 UNIX(R) 下的真实打印。
+
CUPS 的主站是 http://www.cups.org/[http://www.cups.org/]。

HPLIP::
HPLIP， HP Linux(R) 成像及打印系统 (Imaging and Printing system)， 是一套由 HP 开发的用于支持 HP 的打印、 扫描和传真设备的工具。 这套程序利用 CUPS 打印系统作为后端来提供一些打印方面的功能。
+
HPLIP 的主页位于 http://hplipopensource.com/hplip-web/index.html[http://hplipopensource.com/hplip-web/index.html]。

[[printing-troubleshooting]]
== 疑难问题

在使用 man:lptest[1] 进行简单的测试之后， 您可能得到了下面的结果， 而不是正确的结果：

过了一会儿， 它工作了； 或者， 它没有退出一整张纸。::
打印机进行了打印， 但在这之前它呆了一段而且什么都没做。 事实上， 您可能需要按一下打印机上的 打印剩余 或者 送纸 按钮来让结果出现。
+
如果这是问题所在， 打印机可能在等待， 看看在打印之前， 您的任务是否还有更多的数据。 要修正这个问题， 您可以让文本过滤器发送一个送纸字符 (或者其他需要的) 到打印机。 这通常足够让打印机立即打印出内部缓存内剩余的文本。 它同样可以用来确保每个任务的结尾都占用一整张纸， 这样下一个任务才不会在前一个任务最后一张纸的中间开始。
+
接下来的 shell 脚本 [.filename]#/usr/local/libexec/if-simple# 的脚本打印了一个送纸符在它发送任务到打印机之后：
+
[.programlisting]
....
#!/bin/sh
#
# if-simple - Simple text input filter for lpd
# Installed in /usr/local/libexec/if-simple
#
# Simply copies stdin to stdout.  Ignores all filter arguments.
# Writes a form feed character (\f) after printing job.

/bin/cat && printf "\f" && exit 0
exit 2
....

它的输出产生了 "楼梯效果"。::
您可能在纸上得到下面这些：
+
[source,shell]
....
!"#$%&'()*+,-./01234
                "#$%&'()*+,-./012345
                                 #$%&'()*+,-./0123456
....
+
您也成为了 _楼梯效果_ 的受害者， 这是由对新行的标志字符的解释不一致造成的。 UNIX(R) 风格的操作系统使用一个单个字符： ASCII 码 10， 即换行 (LF)。 MS-DOS(R)， OS/2(R)， 和其他的系统使用一对儿字符， ASCII 码 10 _和_ ASCII 码 13 (回车 CR)。 许多打印机使用 MS-DOS(R) 的习惯来代表新行。
+
当您在 FreeBSD 上打印时， 您的文本仅用了换行字符。 打印机， 打印机看到换行字符后， 走一行纸， 但还光标位置还是在这张纸上要打印的下一个字符处。 这就是回车的作用： 将下一个要打印的字符的位置移到纸张的左边缘。
+
这里是 FreeBSD 想要打印机做的：
+
[.informaltable]
[cols="1,1", frame="none"]
|===

|打印机收到 CR
|打印机打印 CR

|打印机收到 LF
|打印机打印 CR + LF
|===
+
下面有几种完成这个的办法：

** 使用打印机的配置开关或者控制面板来更改它对这些字符的解释。 查看打印机的手册来找到怎样更改。
+
[NOTE]
====
如果您引导您的系统到其他除了 FreeBSD 之外的操作系统， 您可能不得不 _重新配置_ 打印机使用 这个操作系统对 CR 和 LF 字符的解释。 您可能更喜欢下面这另一种解决方案。
====

** 让 FreeBSD 的串口驱动自动转换 LF 到 CR+LF。 当然， 这 _仅仅_ 工作在串口打印机上。 要开启这个功能， 定义 `ms#` 变量并 设置 `onlcr` 模式在 [.filename]#/etc/printcap# 文件中相应打印机处。
** 发送一个 _转义码_ 到打印机来让它临时对 LF 字符做不同的处理。 参考您的打印机手册来了解您的打印机支持哪些转义码。 当您找到合适的转义码， 修改文本过滤器让其先发送这个转义码， 然后再发送打印任务。
+ 
这里是一个为懂得 Hewlett-Packard PCL 转义码的打印机编写的文本过滤器。 这个过滤器使得打印机将 LF 作为一个 LF 和一个 CR 来对待； 然后它发送任务； 最后发送一个送纸符弹出任务的最后一张纸。 它应该可以在几乎所有 Hewlett Packard 打印机上工作。
+
[.programlisting]
....
#!/bin/sh
#
# hpif - Simple text input filter for lpd for HP-PCL based printers
# Installed in /usr/local/libexec/hpif
#
# Simply copies stdin to stdout.  Ignores all filter arguments.
# Tells printer to treat LF as CR+LF.  Ejects the page when done.

printf "\033&k2G" && cat && printf "\033&l0H" && exit 0
exit 2
....
+ 
下面是一个 [.filename]#/etc/printcap# 文件的例子在叫做 `orchid` 的主机上。 它只有一台打印机连接在第一个并口上， 一台 Hewlett Packard LaserJet 3Si 名字叫做 `teak`。 它使用上面那段脚本作为文本过滤器：
+
[.programlisting]
....
#
#  /etc/printcap for host orchid
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
        :lp=/dev/lpt0:sh:sd=/var/spool/lpd/teak:mx#0:\
        :if=/usr/local/libexec/hpif:
....

行行覆盖。::
打印机从来不进纸换行。 所有的文本都打印在头一行文本的上面。
+
这个问题是 "相反" 于楼梯效果， 像上面描述的那样， 并且更少见。 一些地方， LF 这个 FreeBSD 用来结束一行的字符被作为 CR 这个将打印位置返回到纸的左边的字符对待。 而没有向下走纸一行。
+
使用打印机的配置开关或者控制面板来强制对 LF 和 CR 进行下面的转换：
+
[.informaltable]
[cols="1,1", frame="none", options="header"]
|===
| 打印机收到
| 打印机打印

|CR
|CR

|LF
|CR + LF
|===
打印丢掉字符。::
当打印时， 每行里打印机都丢掉一些字符没有打。 这个问题可能随着打印的进行越发严重， 丢掉越来越多的字符。
+
这个问题是由打印机跟不上计算机通过串口发送数据的速度造成的 (这个问题应该不会发生在并口打印机上)。 有两种方法能克服这个问题：

** 如果打印机支持 XON/XOFF 流量控制， 那就让 FreeBSD 使用它， 通过加入 `ixon` 模式在 `ms#` 变量里。
** 如果打印机支持请求/清除硬件握手信号 （通常时 `RTS/CTS`）， 指定 `crtscts` 模式在 `ms#` 变量里。 并且要确定连接打印机和计算机的线是支持硬件流量控制的。

它打印出垃圾。::
打印机打印出的东西看起来是一些随机的字符， 而不是想要打印的东西。
+
这通常意味着另一种串口打印机通讯参数设置不正确的错误。 复查 `br` 变量中设定的波特， 和 `ms#` 中的校验设置； 确定打印机也在使用和 [.filename]#/etc/printcap# 文件中相同的设置。

没有反应。::
如果没有反应， 问题就可能出在 FreeBSD 而不是硬件上了。 增加日志文件 (`lf`) 变量到 [.filename]#/etc/printcap# 文件里出现问题的打印机的记录处。 比如， 下面是打印机 `rattan` 的记录， 使用了 `lf` 变量：
+
[.programlisting]
....
rattan|line|diablo|lp|Diablo 630 Line Printer:\
        :sh:sd=/var/spool/lpd/rattan:\
        :lp=/dev/lpt0:\
        :if=/usr/local/libexec/if-simple:\
        :lf=/var/log/rattan.log
....
+
然后， 再次打印。 检查日志文件 (在我们的例子当中， 是 [.filename]#/var/log/rattan.log# 这个文件) 来看是否有错误信息出现。 根据出现的信息， 试着来修正问题。
+
如果您没有指定 `lf` 变量， LPD 会使用 [.filename]#/dev/console# 作为默认值。
