上一部分已经解决了“内容怎样排好”这个问题:文本可以经过 fmt、pr、printf 或排版工具,生成终端预览、纯文本、PostScript、HTML 或 PDF。到了打印环节,我们要处理的是另一件事:把一份经过核验的输入,交给正确的目标,并证明它穿过队列、转换链和传输通道,最终按预期落到纸张或其他输出介质上。
最容易踩的坑,是把 lp 返回任务编号当成“打印成功”。这个返回值只证明调度器接受了请求。任务可能仍在等待、被暂挂、卡在过滤器、无法连接设备,甚至被设备接收后因为缺纸而停止。我们会把每一层需要的证据拆开,并在隔离环境里实际运行 CUPS 客户端、临时调度器、过滤器和受控文件后端。
本章不会要求真实打印设备,也不会假设某个桌面设置界面存在。命令示例都从协议、队列和任务对象出发;涉及管理员操作时会明确标出权限边界。
一份 PDF 能被 file 识别,pdfinfo 能读到页数,页面渲染也没有缺字,说明我们获得了可验证的打印输入。这些检查很重要,因为打印系统无法替我们修复错误页码、错误字体或被截断的表格。但它们没有创建队列任务,也没有触碰任何设备。
可以把打印成功拆成四道门:
为什么要分开?因为每一层都可能“退出 0”,却把问题留给下一层。比如一个合法 PDF 可以包含缺失字形;调度器可以接受一个目标不支持的双面选项;设备也可能把数据收进缓冲区后才报告卡纸。
completed 是调度器看到的任务终态,语义通常是过滤器与后端已经正常结束。它仍不等于“纸张已经被正确拿到”。有些设备会先接收完整任务,再异步成像;有些后端无法取得每一张纸的真实完成信息;某些虚拟目标本来就只生成文件。
因此,交付检查要写成具体条件,而不是一句“打印正常”:
应用把两类信息交给打印系统:文档数据,以及用户意图。用户意图包括目标、份数、介质、双面方式、页面范围、质量和任务名称。CUPS 调度器接收请求后创建 Job 对象,将文档放入 spool 区,并根据优先级、暂挂时间和队列状态决定何时处理。
队列不是打印机的同义词。它是一个逻辑目标,可以指向:
所以,命令中的 office-color 首先是目标名称。要知道它最终通往哪里,需要查设备 URI,而不是由名称猜测。

任务轮到处理时,调度器根据输入格式与目标能力选择转换路径。过滤器可以把纯文本排成页面,把图像缩放到介质,把 PDF 或 PostScript 转成目标格式。RIP,也就是光栅图像处理,会把字体、路径和图像解释成像素页面;它可能发生在主机、Printer Application、打印服务器或设备内部。
过滤器和后端要分清:
如果过滤器失败,通常能在带任务编号的错误日志里看到转换程序与退出状态;如果后端失败,则更常见 URI、名称解析、连接、TLS、认证或设备响应问题。
cupsd 是 CUPS 调度器。客户端、另一个服务器或设备通过 IPP 提交操作;IPP 通常运行在 HTTP 语义之上,常用 TCP 631。它不是“把文件丢到一个端口”这么简单,而是定义了 Printer、Job、Document 等对象,以及查询属性、创建任务、发送文档、取消、暂挂和释放等操作。
IPP 里的 Printer 是逻辑对象,可以代表物理设备,也可以代表服务或网关。Job 由 Printer 创建,保存任务描述、处理意图、状态和零个或多个 Document。这样的模型有两个好处:

lp 成功提交后通常输出类似:
request id is office-color-42 (1 file(s))office-color-42 是 CUPS 便于操作的完整标识。IPP 的数值 Job ID 只保证在创建它的 Printer 上下文中唯一,因此记录完整的“目标-编号”比只抄 42 更稳妥。尤其当多个队列同时有任务时,裸编号很容易指错对象。
常见状态可以这样理解:
“无驱动”并不是没有软件参与。它表示客户端不必为每个型号安装一套厂商专用 PPD 和二进制过滤器。设备或打印服务通过 IPP 动态声明能力,客户端以标准文档格式提交,并根据需要完成通用转换。
IPP Everywhere 组合了设备发现、IPP 能力查询和标准格式。典型设备会声明 PWG Raster,彩色设备还可能声明 JPEG;PDF 和 IPP-USB 也很常见。AirPrint 同样强调发现、能力选择和无需额外安装型号驱动。二者属于相近的无驱动生态,但不能把一个名称机械替换成另一个,也不能仅凭设备名推断支持全部选项。
选择无驱动目标时,至少核对:
document-format-supported 包含准备提交的格式,或系统有明确转换路径;media-supported、sides-supported、print-quality-supported 与任务意图一致;
传统路径由 PPD 描述纸张、分辨率、双面和选项约束,再由过滤程序生成 PCL、ESC/P、标签语言或厂商格式。这能支持很多旧设备,却有三个长期成本:
如果旧设备仍必须保留,更清晰的结构是让 Printer Application 包装旧驱动,对外呈现一个 IPP 目标。这样客户端继续使用标准能力查询和任务模型,专用转换被限制在一个可升级、可审计的边界内。
raw 队列只适合已经由受控应用生成设备就绪数据的极少数场景。把任意 PDF 或文本送入 raw 队列,轻则打印乱码,重则把未经检查的控制数据送往设备。CUPS 已将 raw 队列和传统驱动接口放入弃用路径,不应把它们当作新部署默认方案。
CUPS 能识别文本、PDF、PostScript 和多种图像,但“能识别”只表示系统知道从哪里开始。最终要生成什么,取决于目标声明:
PostScript 是页面描述程序,不是已经展开的像素。能原生解释 PostScript 的设备可以在设备侧 RIP;不具备解释器的设备需要上游转换。PDF 也类似:有些 IPP 目标直接接收 PDF,有些链路会将其转为 PWG Raster 或其他栅格格式。

cupsfilter 可以在不提交队列任务的情况下调用 CUPS 过滤链。下面把受控文本转为 PDF:
cupsfilter -m application/pdf report.txt > report.preview.pdf 2> report.filter.log
file report.preview.pdf
pdfinfo report.preview.pdf
pdftoppm -png -singlefile report.preview.pdf report-preview如果要借用某个目标的能力,可以增加 -d 目标;如果只想查看会运行哪些过滤器,可以使用 --list-filters。但这里有三条边界:
cupsfilter 在当前用户和安全会话中运行,和调度器使用的 lp 用户、环境变量及权限可能不同。先执行一组只读查询:
lpstat -r
lpstat -p -d
lpstat -a
lpstat -v
lpstat -e它们分别回答:
-r:调度器是否可达;-p:已配置目标是否 enabled,以及最近状态消息;-d:当前默认目标;-a:哪些队列 accepting,也就是接收新任务;-v:目标对应的设备 URI;-e:当前可用的目的地,包括发现到的网络目标。enabled 与 accepting 必须分开看。队列可以因维护而停止处理,但继续接收任务;也可以保持处理已有任务,却拒绝新任务。隔离实验实际得到“disabled 但 accepting”的组合,新任务成功进入队列并保持等待。
lp 选择默认目标时,会先看环境变量 LPDEST 和 PRINTER,再看当前用户通过 lpoptions 保存的默认值,最后才是管理员设置的系统默认值。这意味着“同一条 lp report.pdf 在两个会话里去向不同”并不神秘,先打印这些默认来源即可。
显式指定目标更适合脚本:
lp -d office-color -- report.pdf
lpr -P office-color report.pdf再查询目标选项:
lpoptions -p office-color -l传统或兼容队列常通过这条命令列出 PPD 选项。无驱动目标可能由动态 IPP 属性提供能力;如果 lpoptions -l 没有返回列表,不能推断“设备没有双面”,而应查询 IPP 的 *-supported 属性。管理员可在已授权网络中使用 ipptool 读取 Get-Printer-Attributes;具体 URI 必须来自发现或配置结果。

CUPS 同时提供 System V 风格的 lp 和 Berkeley 风格的 lpr。两者最终都向调度器创建任务:
lp -d office-color -- report.pdf
lpr -P office-color report.pdf这里最容易混淆的是大写 -P:
lpr 中,-P office-color 指定目标。lp 中,目标使用小写 -d;大写 -P 是页面列表的兼容选项。两条命令都能从标准输入读取,但只有在上游确实产生数据时才会创建有意义的文档。脚本提交普通文件时,优先传路径,并用 -- 结束 lp 选项,避免以连字符开头的文件名被误解。成功后立即保存返回的完整任务 ID。
一条较完整的 lp 命令可以写成:
lp -d office-color \
-n 2 \
-o media=A4 \
-o sides=two-sided-long-edge \
-o page-ranges=1-4,7 \
-o print-quality=4 \
-- report.pdf对应的 lpr 份数语法是 -#2,其他通用任务选项仍用 -o name=value。常见值如下:
page-ranges 面向输出页,不保证等于文档正文里印出的页码。只要同时使用封面页、N-up 或排版工具重编号,两者就可能不同。正确顺序是先生成最终可打印文件并看预览,再按输出页选择范围。
lpstat 适合组合查询,lpq 适合快速观察某个 Berkeley 风格队列。常用命令是:
lpstat -W not-completed -o office-color
lpstat -W completed -o office-color
lpstat -l -o office-color
lpq -P office-color -l-W not-completed 和 -W completed 要放在 -o 与目标名之前,避免客户端按默认范围解释。前者关注 pending、held、processing 等活动任务;后者能看到 stopped、canceled、aborted、completed 等终态。长列表可能包含任务名、所有者、大小、提交时间和最近消息。
lpq -P office-color 会显示 Rank、Owner、Job、文件名和大小。它的 “ready” 只表示队列当前可处理,不替代设备耗材和成品检查。需要短时间跟踪时可以使用 lpq -P office-color +5 每 5 秒刷新,队列清空后退出;自动化系统更适合轮询 IPP 属性并设置超时,避免无限等待。

拿到完整任务 ID 后,可以使用:
cancel office-color-42
lprm -P office-color 42普通用户通常只能取消自己的任务,操作员或管理员能否取消其他人的任务由 CUPS policy 决定。不要把 -U other-user 或 cancel -u other-user 当成权限绕过;它们改变请求身份或筛选对象,服务端仍会认证和授权。
敏感任务可考虑:
cancel -x office-color-42-x 请求在取消的同时删除任务数据文件。它仍不保证已经进入设备缓冲区的页面能被追回,所以取消后要观察设备是否停止,并按组织策略处理已经输出的纸张。
暂挂与恢复适合“先进入队列,确认后再打”:
lp -H hold -d office-color -- report.pdf
lp -i office-color-43 -H resumelpr 可以用 -o job-hold-until=indefinite 创建暂挂任务。释放前要核对任务所有者、目标和标题,避免把同名文件发到错误设备。
一次性选项写在 lp 或 lpr 命令上,只影响当前任务:
lp -d office-color -o media=A4 -o sides=one-sided -- draft.pdf普通用户使用 lpoptions 保存默认目标或选项,通常写入 ~/.cups/lpoptions:
lpoptions -d office-color
lpoptions -p office-color -o media=A4 -o sides=two-sided-long-edge
lpoptions -p office-colorroot 运行 lpoptions 时,作用域转为所有用户,配置位于 /etc/cups/lpoptions。这和管理员通过 lpadmin -d 设置系统默认 Printer 不是完全相同的概念。生产变更要先备份、审阅差异,并在低风险任务上验证。
最重要的规则是:默认值只是“没有显式指定时的选择”,不能覆盖目标能力。即使 lpoptions 成功保存 sides=two-sided-long-edge,目标没有声明双面时,也不能据此宣称任务会双面输出。隔离实验中的 raw 队列就能保存 media=A4 与 sides=...,但无法列出 PPD 能力。
CUPS 支持 目标/实例 形式保存一组选项:
lpoptions -p office-color/duplex-a4 \
-o media=A4 \
-o sides=two-sided-long-edge
lp -d office-color/duplex-a4 -- report.pdf实例是客户端保存的选项集合,不是新设备,也不会自动继承主目标后来修改的所有选项。它适合为“草稿”“双面 A4”“照片”建立清晰入口,但名称里应表达意图,并安排定期核对:
网络目标常通过 DNS-SD 发布 _ipp._tcp 或 _ipps._tcp 服务。可以先查看可用目的地:
lpstat -e管理员在明确授权的网络中还可以查询发现后端:
lpinfo --include-schemes dnssd -v发现结果可能给出 dnssd://... 候选 URI,CUPS 再解析到实际 IPP 服务。手工配置时常见 URI 类似:
ipp://print.example/ipp/print
ipps://print.example/ipp/printipp 是 IPP over HTTP,可以根据策略升级到 TLS;ipps 从连接开始使用 TLS。AppSocket 与 LPD 仍可连接旧设备,却缺少 IPP 这种标准能力、任务和状态语义。新部署优先选择能查询能力并安全认证的 IPP 路径。
发现不是身份验证。相同显示名称可能来自错误子网、临时热点或恶意服务。创建队列前要核对主机名、证书、设备位置、UUID 和支持属性;打印第一份小任务后再验证设备侧标识。
CUPS 客户端命令中的 -E 表示强制加密与服务器的连接:
lpstat -E -h print.example:631 -p
lp -E -h print.example:631 -d office-color -- report.pdf这里的 -E 不是 enable queue。启用或停止队列由 cupsenable、cupsdisable 等管理操作控制。
安全连接需要同时回答:
Basic 认证只有配合 TLS 才能保护可恢复的凭据。把密码直接写进命令行、脚本或设备 URI,还会泄露到历史、进程列表或日志。客户端策略可以选择 TOFU 或严格 CA 验证;跨组织、跨不可信网络或处理敏感文档时,应明确使用严格证书验证,而不是遇到错误就关闭校验。

打印服务处理的是用户文档,不是一个无害的设备开关。策略至少要区分:
共享目标还要限制来源网络、用户组、最大任务大小、页数或配额。攻击者不必执行代码,只要不断提交超大或无限页任务,就能占满 spool、耗材和队列。权限策略、配额和告警应一起设计。
对于安全打印,held 或“到设备再释放”的流程能减少纸张无人看管,但它不是万能加密。标题、用户名、页数和任务时间仍可能出现在队列或日志中;释放设备也需要正确身份验证。
CUPS 可以保留完成任务的文档文件与历史。常见配置项包括:
PreserveJobFiles:完成后是否以及保留多久的文档数据;PreserveJobHistory:任务属性和终态保留多久;JobPrivateAccess:哪些主体能读取私有任务属性;JobPrivateValues:任务名、来源主机、用户名等哪些字段视为私有。这些默认值可能被发行版或组织策略覆盖,不能只记一个固定数字。敏感环境应读取实际 cupsd.conf 和生效配置,明确最小保留时间、备份与日志轮换。
三类日志也可能暴露元数据:
因此,“取消任务”与“清理痕迹”是两步。对敏感任务使用 cancel -x,再按策略检查历史、日志、设备缓存和已经输出的纸张;不要直接手工删除 spool 文件破坏调度器一致性。
我建议固定使用下面的顺序。它的好处是:每一步都能排除一整类原因,也不会一遇到故障就重建队列。
先执行 lpstat -r。如果输出调度器未运行或无法连接,任务还没有进入任何队列;再根据部署方式检查服务管理器、配置语法和启动日志。
执行 lpstat -p -d 与 lpstat -v,确认目标存在、默认目标正确、队列是否 enabled,以及 URI 是否指向预期端点。
执行 lpstat -a,单独确认队列是否 accepting。not accepting 会让新任务直接失败;disabled 可能仍允许任务排队。

调试日志可能包含文档名、用户和 URI,只在需要时临时提高级别,复现后立即恢复,并按敏感级别控制读取与保留。
实验使用 debian:bookworm-slim,安装 CUPS 2.4.2、cups-filters 1.28.17、Ghostscript 10.0 和 Poppler 22.12。所有配置、状态、spool、日志、证书、输出和临时文件都放在 /tmp/welearn-ch22。
安全限制是实验设计的一部分:
cupsd 只监听 127.0.0.1:6631;sandboxfile: 后端只接受 /tmp/welearn-ch22/output/ 下的目标;--rm 运行,结束后删除队列、停止服务和清除目录。为什么不用随便一个 file: 队列?当前 Debian 包没有传统 file 后端。第一次实验中,调度器把 raw 任务标成 completed,却没有产生输出文件。这个失败很有价值:它证明 completed 不能替代后端交付证据。白名单后端真正收到 116 字节并生成相同 SHA-256 后,我们才确认调度、spool 与后端链路闭合。
没有调度器时。 lpstat -r 退出码为 0,但文字是 scheduler is not running;lp 和 lpr 退出 1 并报告没有默认目标,lpq 与 cancel 报无法连接。脚本必须同时读取退出码和消息。
转换成功时。 cupsfilter -m application/pdf 生成 PDF 1.3、1 页、Letter 612×792 pt,pdftoppm 生成 816×1056 RGB PNG。文本提取却出现错误字符映射,因此“格式合法”“能渲染”“文字可提取”必须分别检查。
队列状态时。 cupsdisable 后目标为 disabled,lpstat -a 仍显示 accepting;新任务获得 SandboxQueue-2 并留在队列。执行 cupsreject 后,提交才以 Destination ... is not accepting jobs 失败。
取消与默认项时。 cancel SandboxQueue-2 和 lprm -P SandboxQueue 3 都移除了对应任务。普通用户保存 media=A4 sides=two-sided-long-edge 后,~/.cups/lpoptions 如实出现这两个值;但 raw 目标无法提供 PPD 能力列表,说明保存选项不是能力证明。
实验最后取消所有任务、删除队列、停止调度器、确认 6631 不再监听,再删除 /tmp/welearn-ch22。容器返回后,--rm 也删除了容器实例。
提交前:
lpstat -r -p -d -a -v 确认调度器、目标、默认值、接收、启用和 URI。提交时:
提交后:
lpstat 或 lpq 跟踪任务状态,区分 held、stopped、pending 与 processing。cancel -x,并检查 spool、历史、日志、设备缓存和已输出纸张的处理策略。保存提交返回的完整任务 ID,再用 lpstat -W not-completed -l -o 目标 或 lpq -P 目标 -l 读取 pending、held、processing 与最近消息。
如果任务进入 processing 后停止,用任务编号筛选 error_log,找第一个失败的过滤器或后端;不要被后续连锁报错带偏。
后端开始连接后,核对 DNS、路由、URI、端口、TLS、认证与授权,再读取 printer-state-reasons 中的 timed-out、media-empty、media-jam 等原因。
最后检查设备成品:目标、页数、份数、双面、介质、字形和任务归属。completed 只有和这一步对上,交付证据才闭合。