FreeBSD 使用手册
上一页 | 第9章 　打印 | 下一页

# 9.4 高级设置

> 警告: 从 FreeBSD 8.0 起， 串口对应的设备名由 `/dev/ttyd`N`` 变为 `/dev/ttyu`N``。 FreeBSD 7.X 用户应将这篇文档的示例中的设备名改为原先的样子。

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

## 9.4.1 过滤器

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

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

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

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

- 在 过滤器是如何工作的 小节中将介绍在打印过程中过滤器的作用。 如果希望了解在 LPD 使用过滤器时， 在 “幕后” 发生的事情， 便应阅读这一小节。 了解这些知识能够帮助您在为打印机安装过滤器时更快地排查可能会遇到的各种问题。

- LPD 假定任何打印机在默认状态下均能打印纯文本内容。 对于不能直接打印纯文本的 PostScript® 打印机 (以及其他基于打印语言的打印机) 而言这会带来问题。 在 在 PostScript 打印机上使用纯文本任务 这节中将会介绍如何解决这个问题的方法。 如果您使用 PostScript 打印机， 就应阅读这节内容。

- PostScript 对于许多程序来说都是一个非常受欢迎的输出格式。 一些人甚至直接写 PostScript 代码。 但不幸的是， PostScript 打印机非常昂贵。 模拟 PostScript 在 非 PostScript 打印机上 这节将告诉您怎样进一步修改 打印机的文本过滤器， 使得一台 非 PostScript 打印机接受 并打印 PostScript 数据。 如果 您没有 PostScript 打印机， 那么您应该阅读这个小节。

- 转换过滤器 这节讲述了一个自动把指定格式文件， 比如图像或排版数据， 转换成您打印机可以理解的格式的方法。 在阅读了这节之后， 您就应该可以配置打印机， 让用户可以用 `lpr `-t`` 来打印 troff 数据、 用 `lpr `-d`` 来打印 TeX DVI 数据， 或用 `lpr `-v`` 来打印光栅图像数据等工作了。 建议您阅读这节。

- 输出 过滤器 这节讲述了这个不是经常使用的 LPD: 的功能－输出过滤器。 除非您要打印页眉 (见 页眉 这节 )， 您或许可以完全跳过这节。

- lpf: 一个文本过滤器 描述了 `lpf`， 一个 FreeBSD 自带的相当完整而又简单的文本过滤器， 可以使用在行式打印机(和那些担当行式打印机功能的激光打印机 )上。 如果您需要一个快速的方法来让打印机统计打印纯文本的工作量， 或者您有一台遇到退格字符就冒烟的打印机， 您应该考虑 `lpf`。

> 注意: 您可以在 `/usr/share/examples/printing` 目录中找到下面将提到的那些脚本的副本。

### 9.4.1.1 过滤器是怎样工作的

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

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

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

在 `/etc/printcap` 文件中， 您可以指定三种过滤器：

- The 文本过滤器 ， 在 LPD 文档中也叫做 输入过滤器 ， 处理 常规的文本打印。 可以把它想象成默认过滤器。 LPD 假定每台打印机默认情况下都可以打印纯文本， 而文本过滤器的任务就是来搞定退格、 跳格， 或者其他在某种打印机上容易错误的特殊字符。 如果您所在的环境对打印机的使用情况进行记帐， 那么文本过滤器必须也对打印的页数进行统计， 通常是根据打印的行数和打印机在每页上能打印的行数进行计算得出。 文本过滤器的启动命令为：

  `filter-name` [-c] -w `width` -l `length` -i `indent` -n `login` -h `host` `acct-file`

  这里

  `-c`

    当任务用 `lpr `-l`` 这个命令提交时出现

  `width`

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

  `length`

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

  `indent`

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

  `login`

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

  `host`

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

  `acct-file`

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

- 转换过滤器 的功能是， 将特定格式的文件转换成打印机能够识别并打印的格式。 例如， ditroff 格式的排版数据就是无法直接打印的， 但您可以安装一个转换过滤器来将 ditroff 文件转换成一种打印机可以识别和打印的形式。 请参见 转换过滤器 这一节来了解更多细节。 如果您需要对打印进行记帐， 那么转换过滤器也必须完成记帐工作。 转换过虑器的启动命令为：

  `filter-name` -x `pixel-width` -y `pixel-height` -n `login` -h `host` `acct-file`

  这其中 `pixel-width` 的值来自 `px` 标签 (默认为 0)， 而 `pixel-height` 的值来自 `py` 标签 (默认为 0)。

- 输出过滤器 仅在没有文本过滤器时， 或者报头页被打开时使用。 就我们的经验而言， 输出过滤器是很少用到的. 在 输出过滤器 这节中会介绍它们。 启动输出过滤器的命令行只有两个参数：

  `filter-name` -w `width` -l `length`

  它们的作用与文本过滤器的 `-w` 和 `-l` 参数是一样的。

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

exit 0

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

exit 1

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

exit 2

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

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

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

### 9.4.1.2 在 PostScript® 打印机上打印纯文本任务

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

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

我们怎样去做？

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

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

    :if=/usr/local/libexec/psif:

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

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

    #!/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。 您可以使用任何您喜欢的文本到 PostScript 转换程序。 FreeBSD Ports Collection (详见 Ports Collection) 中包含了一个功能非常完整的文本到 PostScript 的转换程序， 它叫做 `a2ps`。

### 9.4.1.3 模拟 PostScript 在非 PostScript 打印机上

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

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

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

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

    #!/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` 标签完成:

    :if=/usr/local/libexec/ifhp:

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

### 9.4.1.4 转换过滤器

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

#### 9.4.1.4.1 为什么安装转换过滤器?

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

    % dvips seaweed-analysis.dvi
    % lpr seaweed-analysis.ps

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

    % lpr -d seaweed-analysis.dvi

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

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

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

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

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

文件类型 | 在`/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``就是指 打印机需要在`/etc/printcap`文件中 `df`变量所指的过滤器。

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

#### 9.4.1.4.3 安装转换过滤器

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

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

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

    #
    #  /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 脚本， 名字叫做 `/usr/local/libexec/psdf`。 下面是它的代码：

    #!/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 文本过滤器 `lprps` (详见 在 PostScript 打印机上打印纯文本任务 这一节)， 并且带着 LPD 传给脚本的全部参数。 `lprps` 工具将利用这些参数来为打印进行记帐。

#### 9.4.1.4.4 更多转换过滤器应用实例

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

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

    #!/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- 兼容的数据。

下面是为打印机配置上上述过滤器的 `/etc/printcap` 文件：

    #
    #  /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 打印机上打印用 groff 排版软件生成的 troff 数据的打印过滤器：

    #!/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` 来与打印机进行通讯。 如果打印机是接在并口上的， 那么就应该使用下面的这段脚本：

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

这里是我们要启用过滤器需要在 `/etc/printcap` 里增加的内容：

    :tf=/usr/local/libexec/pstf:

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

    #!/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

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

    :rf=/usr/local/libexec/hprf:

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

    :df=/usr/local/libexec/hpdf:

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

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

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

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

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

    #!/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

#### 9.4.1.4.5 自动转换： 一种替代转换过滤器的方法

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

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

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

### 9.4.1.5 输出过滤器

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

- LPD 为整个任务启动一个输出过滤器， 而不是为任务中的每个文件都启动一次。

- LPD 不会提供任务中文件开始和结束的信息给输出过滤器。

- LPD 不会提供用户名或者主机名给过滤器， 所以它是无法做打印记帐的。 事实上它只有两个参数：

  `过滤器-名字` -w`宽度` -l`长度`

  `宽度` 来自于 `pw` 变量， 而 `length` 来自于 `pl` 变量， 这些值都是实际问题中给打印机设置的。

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

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

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

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

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

### 9.4.1.6 `lpf`： 一个文本过滤器

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

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

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

## 9.4.2 报头页

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

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

LPD 系统可以自动为您的打印提供报头页， 如果 您的打印机可以直接打印纯文本。 如果您的打印机是一台 PostScript 打印机， 您将需要一个外部的程序来生成报头页； 详见 在 PostScript 打印机上打印报头页。

### 9.4.2.1 打开报头页

在 简单打印设置 这节， 我们通过在 `/etc/printcap` 文件中指定 `sh` (“禁止报头页”) 来把报头页功能关掉了。 要重新为打印机开启报头页功能， 只需要删除掉 `sh`。

听起来很容易， 不是么？

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

    #!/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` 变量指定输出过滤器的路径。 参见 输出过滤器 这一节来得到更多信息。

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

    #
    #  /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`` 命令来提交任务； 参考 报头页选项 这一节来得到更多关于 lpr(1) 的选项。

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

### 9.4.2.2 控制报头页

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

          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` (禁止换纸) 在 `/etc/printcap` 文件里目标打印机的记录中)。

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

    rose:kelly  Job: outline  Date: Sun Sep 17 11:07:51 1995

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

### 9.4.2.3 为带报头页的任务记帐

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

为什么？

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

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

这样， 您该怎么办呢？

您可以：

- 认可 LPD 的这个问题， 并且免费提供报头页打印。

- 安装一个替代 LPD 的软件， 比如 LPRng。 参考 替换标准的后台打印软件 来得到更多关于可以替代 LPD 的软件的信息。

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

### 9.4.2.4 在 PostScript 打印机上打印报头页

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

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

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

    #!/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 转换过滤器， 被修改之后来生成一个报头页：

    #!/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

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

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

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

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

## 9.4.3 网络打印

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

- 访问一台连接在远程主机上的打印机。 在一台主机上安装一台常规的串口或并口打印机。 然后， 设置 LPD 来通过网络访问其他主机上的打印机。 具体见 安装在远程主机上的打印机 这节。

- 访问一台直接连接在网络上的打印机。 打印机另有一个网络接口 (或者替代常规的串口或者并口)。 这样的打印机可能像下面这样工作：

  - 它或许可以理解 LPD 的协议， 并且甚至可以接收远程主机发来的任务排进队列。 这样， 它就像一个普通的主机运行着 LPD 一样。 做在 安装在远程主机上的打印机 里介绍的步骤， 可以设置好这样的打印机。

  - 它或许支持网络数据流。 这样， 把打印机 “接” 在一台网络上的主机上， 由这台主机负责安排任务并发送任务到打印机。 参见 带网络数据流接口的打印机 这节来得到更多安装这类打印机的建议。

### 9.4.3.1 安装在远程主机上的打印机

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

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

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

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

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

1. 可以随意给这个记录起名字。 简单起见， 您可以给打印服务器使用相同的名字或者别名。

2. 保留 `lp` 变量为空， (`:lp=:`)。

3. 建立一个后台打印队列目录， 并用 `sd` 变量指明其位置。 LPD 将把任务提交给打印服务器之前， 会把这些任务保存在这里。

4. 在 `rm` 变量中放入打印服务器的名字。

5. 在 `rp` 中放入打印服务器上打印机的名字。

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

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

    #
    #  /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` 上建立一个后台打印队列目录：

    # 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` 上输入了：

    % lpr -P bamboo -d sushi-review.dvi

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

### 9.4.3.2 带有网络数据流接口的打印机

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

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

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

    #!/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`。 这里是为这个打印机写的文本过滤器：

    #!/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

## 9.4.4 限制打印机的使用

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

### 9.4.4.1 限制多份副本

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

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

    lpr: multiple copies are not allowed

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

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

    #
    #  /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` 的 `/etc/printcap` 文件中 (顺便我们也禁止打印机 `teak` 多份打印) ：

    #
    #  /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 `-#`` 命令的使用， 但仍然没有禁止用户多次运行 lpr(1) ， 或者多次提交任务中同样的文件， 像下面这样：

    % lpr forsale.sign forsale.sign forsale.sign forsale.sign forsale.sign

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

### 9.4.4.2 限制对打印机的访问

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

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

    lpr: Not a member of the restricted group

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

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

    #
    #  /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 `/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.

> 注意: 这里每台仅能有一个限制的组。

### 9.4.4.3 控制提交的任务大小

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

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

> 注意: 这个限制是对于任务中 文件 的， 而 不是 任务总共的大小。

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

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

    #
    #  /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` 变量在远程主机的 `/etc/printcap` 文件中。 参见 安装在远程主机上的打印机 这一节来得到更多有关远程打印的信息。

除此之外， 还有另一种限制远程任务大小的方法； 参见 限制远程主机打印任务。

### 9.4.4.4 限制远程主机打印任务

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

主机限制

  您可以控制本地 LPD 接收哪台远程主机发来的请求， 通过 `/etc/hosts.equiv` 文件和 `/etc/hosts.lpd` 文件。 LPD 查看是否到来的任务请求来自被这两个文件中列出的主机。 如果没有， LPD 会拒绝这个请求。

  这些文件的格式非常简单： 每行一个主机名。 注意 `/etc/hosts.equiv` 文件也被 ruserok(3) 协议使用， 并影响着 rsh(1) and rcp(1) 等程序， 所以要小心。

  举个例子， 下面是 `/etc/hosts.lpd` 文件在主机 `rose` 上：

      orchid
      violet
      madrigal.fishbaum.de

  意思是主机 `rose` 将接收来自 `orchid`， `violet`， 和 `madrigal.fishbaum.de` 的请求。 如果任何其他的主机试图访问主机 `rose` 的 LPD， 任务将被拒绝。

大小限制

  您可以控制后台打印队列目录需要保留多少空间。 建立一个叫做 `minfree` 的文件在后台打印队列目录下为本地打印机。 在这个文件中插入一个数字来代表多少磁盘块数 (512 字节) 的剩余空间来接收远程任务。

  这让您可以保证远程用户不会填满您的文件系统。 您也可以用它来给本地用户一个优先： 他们可以在磁盘剩余空间低于 `minfree` 文件中的指定值后仍然可以提交任务。

  比如， 让我们增加一个 `minfree` 文件为打印机 `bamboo`。 我们检查 `/etc/printcap` 文件来找到这个打印机的后台打印队列目录； 这里是打印机 `bamboo` 的记录：

      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 可以接受远程任务：

      # echo 6144 > /var/spool/lpd/bamboo/minfree

用户限制

  您可以控制哪些远程用户可以打印到本地打印机， 通过指定 `rs` 变量在 `/etc/printcap` 文件中。 当 `rs` 出现在一个本地打印机的记录中时， LPD 将接收来自远程主机 并 在本地有同样登录名的用户提交的任务。 否则， LPD 会拒绝这个任务。

  这个功能在一个 (比如) 有许多部门共享一个网络的环境中特别有用， 并且有些用户可以越过部门的边界。 通过为他们在您的系统上建立帐号， 他们可以他们自己的部门的系统里使用您的打印机。 如果 只 允许他们您的打印机， 而不是您的计算机资源， 您可以给他们 “象征” 帐户， 不带主目录并且设置一个没用的 shell ， 比如 `/usr/bin/false`。

## 9.4.5 对打印机使用记帐

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

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

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

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

- 定期记帐 是更常用的方法， 可能因为它更简单。 无论合适何人打印一个任务， 过滤器都将记录用户名， 主机名， 和打印的页数到一个记帐文件。 每个月， 学期， 年， 或者任何您想设定的时间段， 收集这些不同打印机上的记帐文件， 按用户对打印的页数进行结算， 并对使用进行付费。 然后删掉所有记录文件， 开始一个新的计费周期。

- 实时记帐 不太常用， 可能因为它比较难。 这种方法让过滤器对用户的打印进行实时的记帐。 像磁盘配额， 记帐是实时的。 您可以组织用户打印当他们的帐户超额的时候， 并且可能提供一种方法让用户检查并调整他们的 “打印配额。” 但这个方法需要一些数据库代码来跟踪用户和他们的配额。

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

### 9.4.5.1 快速并且混乱的打印记帐

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

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

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

    2.00 rose:andy
    3.00 rose:kelly
    3.00 orchid:mary
    5.00 orchid:mary
    2.00 orchid:zhang

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

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

      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

这些是 pac(8) 需要的参数：

`-P`打印机``

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

`-c`

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

`-m`

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

`-p`单价``

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

`-r`

  反向排序。

`-s`

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

`名字` `...`

  只打印指定 `名字` 用户的记帐信息。

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

      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

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

    # pac -p1.50

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

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

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

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

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

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

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

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

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

您该怎样做？

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

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

上一页 | 起始页 | 下一页
基本设置 | 上一级 | 使用打印机

本文档和其它文档可从这里下载：ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题，请先阅读文档，如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.