创建智能打印队列

作者:Mark Plimley

在 1999 年 12 月刊的 Linux Journal 中,Michael Hughes 介绍了在您的 Linux 计算机上设置打印的一些基础知识。本文将更详细地介绍如何使您的打印安装适用于各种打印机。最大的问题是,大多数应用程序,尤其是基于图形界面的应用程序(在 X 窗口系统下运行),都使用 PostScript 打印机语言进行打印,而我们大多数人没有 PostScript 打印机。

我基本上很懒——我希望我的电脑让事情变得更轻松。如果电脑不是用来自动化任务和简化我的生活,那它是用来做什么的呢?我很快就对不得不处理来自 Netscape 或其他应用程序的纯文本文件与 PostScript 文件打印感到沮丧。所以我编写了一个打印过滤器来为我处理这些决策,您也可以这样做。您的 Linux 发行版附带的 BSD 打印系统具有通过过滤器管道传输打印文件的能力,该过滤器可以是任何可执行程序。由于过滤器通常只有简单的任务要完成,因此它们通常编写为 bash 或 Perl 脚本。

打印守护程序将要打印的文件发送到过滤器的标准输入,过滤器根据需要修改文件。然后,过滤器可以将修改后的文件写入标准输出,打印系统将其发送到打印机。您的过滤器可以对文件进行任何类型的修改,您甚至不必将修改后的文件写入标准输出,我们稍后会看到。

BSD 打印系统

特定的打印机功能在 /etc/printcap 文件中定义,该文件通常在安装 Linux 时创建。您使用的每个打印机或打印机配置都必须在此文件中定义。打印机可以本地连接,连接到网络上的另一台计算机或网络打印机。您可以为同一台打印机设置多个配置来处理不同的文件类型,并且打印系统可以将输出发送到其他设备或将其保存到文件。实际上,任何可以接受计算机文件的东西都可以作为输出设备。

您的 printcap 文件中的每个打印机配置都定义一个打印队列。您可以使用 lpr 命令从命令行打印,或者通过各种应用程序中的打印按钮或菜单打印。UNIX 和 Linux 应用程序使用 lpr 命令通过使用行式打印机守护程序 lpd 发出实际的打印请求,lpd 会产生自身的副本以处理每个打印请求。lpd 是主程序,在后台持续运行,以处理来自 lpr、lpq(用于查询打印作业和打印机状态)和 lprm(用于从打印队列中删除作业)的所有请求。

列表 1 是一个典型的 printcap 条目,它定义了一个连接到您的并行打印机端口的打印机。文件中的注释以井号 (#) 字符开头。每个条目定义一个打印队列,并由一系列用冒号 (:) 字符分隔的字段组成。条目的第一个字段提供打印队列的一个或多个名称,用管道 (|) 字符分隔。您应该将“lp”作为您的打印队列之一的名称包含在内,因为它将是您在不指定队列的情况下打印时的默认队列。此第一个字段的最后一个子字段应包含此打印队列的描述,并且可能会被某些打印管理软件显示。我给这个队列起了别名“text”,以便打印命令可以是 lpr filenamelpr -Ptext filename

列表 1

为了便于阅读,我通常将每个字段放在单独的一行上,除了最后一行之外,每行末尾都有反斜杠转义字符。但您不必这样做。传统上,当字段在单独的行上给出时,在每个字段的开头和结尾放置额外的冒号,如本例所示,所有延续行都用制表符缩进。当 lpd 读取此文件时,额外的冒号将被忽略。

第一个字段之后的字段告诉 lpd 此队列的属性。这些后续字段中的每一个都以关键字开头,文本值使用等号 (=) 符号,数值使用井号 (#) 符号,然后是该字段的值。某些关键字是开关,没有值。在列表 1 中,第二个关键字“lp”定义了输出将发送到的设备。在本例中,/dev/lp 应该是指向您的打印机端口的 /dev/lp0 或 /dev/lp1 的符号链接。不要将此字段名称与第一个字段中的默认打印队列名称混淆。字段的顺序无关紧要,除了命名队列的第一个字段。

lf 关键字指定日志文件的完整路径,其中可以记录来自打印守护程序关于此队列的错误。当出现问题时,此文件的内容可能会为您提供问题的线索,尽管许多与打印相关的错误消息将发送到您的系统日志 /var/log/messages。sb 布尔关键字告诉 lpd 打印一个简短的单行“横幅”。最后,sd,假脱机目录关键字,允许您指定放置正在打印文件的临时副本的目录路径。您必须为每个打印队列指定一个假脱机目录。队列的其他控制信息和每个打印作业也放置在此目录中,因此您的 printcap 文件中的每个队列都需要有自己的假脱机目录。

这些是许多可用参数中最常见的。printcap 手册页列出了可用于配置您的打印队列条目的所有参数。

一个简单的打印过滤器

如果我们想修改发送到队列的打印文件,或者在打印之前执行某些其他任务,我们使用输出过滤器 printcap 关键字 of。过滤器程序通常保存在使用它的队列的假脱机目录中,例如 /var/spool/lpd/lp1。列表 2 显示了一个使用简单过滤器的 printcap 条目。打印机端口由 lp= 字段像往常一样定义。当我们使用 of=/var/spool/lpd/filter 字段时,打印守护程序在将原始文件发送到 lp 字段设置的设备之前,会通过此过滤器管道传输该文件。在 lf 字段之后,我使用 sfsh 布尔字段来告诉打印守护程序抑制此队列的换页符和标题页。

列表 2

那么我们可以用我们的打印机过滤器做什么呢?几乎任何您能想到的。列表 3 是一个用于在 HP 打印机上打印纯文本的过滤器。当没有以其本机打印语言(例如 PCL、HPGL 或 Postscript)向 Hewlett-Packard 打印机发送数据时,它们期望 MS-DOS 行尾,该行尾由 ASCII 回车符和换行符(新行)组成。如果您打印 UNIX 样式的文本文件,该文件仅使用换行符来标记行的结尾,则文件的第二行和后续行将不会从纸张的左边缘开始。如果没有回车符来告诉打印机移动到左边缘,您将得到阶梯效应。没有自动换行,因此一旦文本移出纸张的右边缘,所有内容都将丢失到众所周知的位桶中。

列表 3

此过滤器使用 awk 命令在 UNIX 文本的每一行末尾插入回车符和换行符。正如我上面提到的,BSD 打印机守护程序将要打印的文件通过管道传输到过滤器的标准输入。然后,它将过滤器的标准输出传递到 printcap 条目的 lp= 字段上指定的打印设备。

变得更复杂

上面的示例非常适合解决常见的打印问题,但它仅适用于文本文件。如果您不小心将 PostScript 文件或(天哪)图形文件发送到此队列,您将得到大量无用的纸张和一个轻蔑的系统管理员。

列表 4 是我们想要用来解决此问题的过滤器。首先,我们指定一个日志文件,我们可以在其中写入来自脚本的调试信息。接下来,我们使用 cat 命令将标准输入捕获到文件中。然后,我们可以检查此文件,以使用 file 命令确定它是什么类型的文件。如果是文本文件,我们像在上面的简单过滤器中那样处理它。

列表 4

如果文件是 PostScript 文件,我们运行 ghostscript 以将其格式化为我们特定类型的非 Postscript 打印机。在本例中,我指定了一个彩色 Deskjet,它适用于我的 HP Deskjet 660C。当然,如果您有幸拥有一台 PostScript 打印机,您根本不需要此过滤器。

我们过滤器的最后一个分支允许我们打印 TIFF 图形文件。我们只需在 ghostscript 命令前面插入 tiff2ps 命令。通过这种方式,您可以轻松地添加处理其他类型文件的能力,而无需手动格式化它们。您可能需要使用命令的完整路径名,因为 lp 守护程序将不具有登录 shell 所具有的相同路径。

变得更智能

我还希望我的智能打印队列能够处理几台不同的打印机。您看,我使用笔记本电脑处理所有电子邮件和个人计算。当我在家时,我使用旧的 Epson 兼容打印机进行简单的打印任务。如果我需要更高质量的输出,并且我的孩子们允许我从他们的电脑上拔下它,我会连接到 HP Deskjet。当我在工作时,我打印到网络上的 HP Laserjet 4MV PostScript 打印机。

打印机类型的更改很容易通过向过滤器添加有关打印机类型的信息来处理。为了设置可用的打印机类型,我在 $HOME/.profile 中添加了一行,询问我将使用哪种打印机。这在我启动笔记本电脑后第一次登录时运行一次。我输入的打印机类型写入文件 /tmp/printer。我使用 Ghostscript 识别的名称。

列表 5 是使用打印机类型信息的过滤器。我添加了变量 PTYPE 来保存我当前正在使用的打印机的名称。第一级“if”分支检查文件类型,就像在之前的过滤器中一样。在这些分支中,第二级“if”分支检查不同的打印机。

列表 5

过滤器中的另一个更改是,我没有将输出写入标准输出,而是通过管道将其传输到另一个 lpr 打印命令。在 lpr 命令中,我为连接的或网络打印机指定了一个打印队列。这绕过了 printcap 中的 lp= 行。为了使其工作,我为名为“raw”的打印队列创建了另一个 printcap 条目。原始 printcap 条目输出到实际的打印机。要创建原始打印队列,只需采用列表 1,删除 lp 默认名称,并将队列名称更改为 raw。我为网络打印机使用了不同的原始打印队列,因为它是一台 PostScript 打印机。每个原始打印机在 printcap 文件中都有自己的打印队列。连接到并行端口的打印机的原始队列,以及当我在工作时用于网络打印机的 “4mvraw”。

列表 6 是上述过滤器的 printcap 条目及其对应的原始打印队列。与普通打印机队列的条目相比,此条目中不同的一件事是使用 /dev/null 作为打印机设备。由于我们的过滤器将处理输出(通过将过滤后的输出打印到原始设备),因此我们不需要 lpd 将其发送到任何设备。

列表 6

如果您有多个打印机可用,您可以使用此过滤器技术将任何不同类型的文件发送到不同的打印机。如果您有传真调制解调器,您甚至可以为您的打印系统添加“打印到传真”功能。

激活您的新打印队列

在 /etc/printcap 中添加新队列后,您需要告诉打印守护程序重新读取 printcap 文件。列表 7 是我多年前编写的脚本,用于处理激活您的新 printcap 条目所需的额外工作。您可能需要验证您的系统的各种目录和命令的路径名。您必须以超级用户身份运行它。

列表 7

如果您并不总是连接或可用打印机,但您想将某些内容发送到打印队列以便稍后打印怎么办?作业会等到您连接打印机或连接到网络吗?是的,如果您的原始打印机连接到您的并行端口,它会等待,因为 /dev/lp 会等待来自打印机的握手信号。

一些调试技巧

如果作业无法打印,您需要确定问题是出在打印系统还是您的过滤器上。首先要查看的地方是您的系统日志文件 /var/log/messages,然后查看队列的日志文件。接下来,仔细检查您的 printcap 文件中的语法。确保每个条目的除最后一行之外的每一行都以“\”结尾。打开一些调试输出以检查您的过滤器的操作。如果问题出在打印系统上,您可以尝试停止并重新启动打印。您的系统启动区域中应该有一个脚本可以正确地执行此操作。在 SuSE 发行版中,启动和关闭脚本是 /sbin/init.d/lpd。与任何 System V 风格的启动脚本一样,这可以手动运行为 /sbin/init.d/lpd start 或 /sbin/init.d/lpd stop。

当假脱机目录的磁盘已满时,打印队列可能会卡住。这可能会发生在非常大的作业或您的磁盘分区太小的情况下。您将不得不使用 lprm 删除作业以释放它,然后在该磁盘分区上清理不必要的文件,或者将您的假脱机目录移动到更大的分区。在将所有目录和文件复制到新位置后,您可以删除所有旧目录 (rm -r lpd),并将 lpd 目录替换为指向您复制旧 lpd 目录树的新位置的符号链接 (ln -s /path/to/new/location/lpd /var/spool/lpd)。

总结

我们讨论了 BSD 打印的工作原理以及 printcap 配置文件中的一些常用选项。我们向您展示了如何编写过滤器来更改发送到实际打印机的文件,以及如何编写过滤器来自动处理不同类型的输入文件以进行打印。我们还了解到,过滤器可以将输出重定向到不同的设备或文件。希望这能为您提供足够的信息来理解和创建您自己的“智能”打印机队列,并简化您的打印任务。

Mark Plimley (markp@blueneptune.com) 于 1978 年开始使用 Imsai 8080(8 位,2MHz Intel 8080)进行家庭计算。在他作为机械工程师的早期生活中,他使用 FORTRAN 进行编程。自 1992 年以来,他主要担任 UNIX 系统管理员,并自 1995 年 1 月以来一直使用 Linux。当不在电脑屏幕前时,他可以在家里修修补补,与妻子和两个青少年一起做一些活动,或者在教堂帮忙。

本文中引用的所有列表都可以通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue73/3741.tgz 获取。

电子邮件:markp@blueneptune.com

加载 Disqus 评论