11. 命令行

← 标准库 · 目录 · 下一章:实用范例 →

共 12 个命令:4 个处理 PDF,4 个处理脚本,2 个用于分发,2 个用于许可证。

命令 功能
run 用脚本校验 PDF
compare 比较两个版本
watch 监视文件夹并校验新到的文件
fix 应用修改并保存新的 PDF
inspect 快速查看 PDF 概要
lint 不执行地分析脚本
fmt 格式化脚本
doc 由脚本生成文档
pack 打包配置与数据
add 安装软件包
fingerprint 设备指纹
license 许可证校验

退出码

所有执行校验的命令都遵循同一约定。

代码 含义
0 全部通过
1 仅有警告
2 校验错误,或 PDF 无法读取
3 脚本语法错误
pdfl run profile.pdfl file.pdf > report.json
case $? in
  0) echo "approved" ;;
  1) echo "approved with warnings" ;;
  2) echo "rejected — see report.json" ;;
  3) echo "error in the validation script" ;;
esac

pdfl run

用脚本校验 PDF。

pdfl run <script.pdfl> <input.pdf> [options]
选项 默认 功能
--output json|csv|html|pdf json 报告格式
--output-file <file> 写入文件而非标准输出
--fail-on error|warning error 设为 warning 时警告也退出码 2
--verbose 标准错误输出附加信息
pdfl run prepress.pdfl magazine.pdf                                    # 终端 JSON
pdfl run prepress.pdfl magazine.pdf --output html --output-file report.html
pdfl run prepress.pdfl magazine.pdf --output pdf --output-file report.pdf
pdfl run prepress.pdfl magazine.pdf --output csv --output-file findings.csv
pdfl run prepress.pdfl magazine.pdf --fail-on warning                  # 严格模式

JSON 报告

{
  "script_name": "prepress.pdfl",
  "input_file": "magazine.pdf",
  "profile": "offset-magazine",
  "status": "FAIL",
  "total_pages_analyzed": 120,
  "error_count": 2,
  "warning_count": 0,
  "info_count": 0,
  "diagnostics": [
    {
      "id": "PDFL-001",
      "severity": "error",
      "check_name": "Ink coverage",
      "message": "page 7: 324% ink (limit 300%)",
      "line": 12
    }
  ]
}

同一个 PDF 配同一个脚本,总是产生逐字节相同的报告,可用于版本管理和 CI 中的差异比对。


pdfl compare

比较两个版本:文本、结构和元数据。

pdfl compare <v1.pdf> <v2.pdf> [options]
选项 默认 功能
--output json|csv|html|pdf json 格式
--output-file <file> 写入文件
--normalize 忽略大小写和空白
--ignore-dates 比较前遮蔽日期
--similarity-threshold <0-100> 100 可接受的最低相似度
pdfl compare approved_v1.pdf new_v2.pdf --normalize --ignore-dates

# 允许 1% 以内的差异,低于该值即为错误
pdfl compare v1.pdf v2.pdf --similarity-threshold 99 \
  --output html --output-file diff.html

工作原理

  • 页面按内容而非页码对齐:中间插入了一页时,不会把其后的全部页面都报为 差异。可处理超过一千页的文档。
  • 每个对齐的页面都会得到相似度分数,以及变化行的样本(- 删除、+ 新增)。
  • 元数据变化记为警告;文本变化低于阈值记为错误,高于阈值记为警告
  • 报告中的 similarity 字段给出总体分数。
page 4 → 4: similarity 97.8% | -original title | +revised title

pdfl watch

监视文件夹,校验每一个新到或被修改的 PDF。

pdfl watch <folder> --script <script.pdfl> [options]
选项 默认 功能
--pattern <glob> *.pdf 处理哪些文件
--exclude <glob> 排除哪些文件
--output-dir <folder> 与 PDF 同目录 报告输出位置
--depth <n> 1 子目录深度
--debounce <ms> 1000 等待文件稳定的时间
--report json|csv|html|pdf json 报告格式
--fail-fast 遇到第一个错误即停止
--once 处理现有文件后退出
# 印刷厂的收件夹,持续运行
pdfl watch inbox/ --script preflight.pdfl --output-dir reports/ --report html

# CI 的批处理:处理完毕后以最差的退出码退出
pdfl watch inbox/ --script preflight.pdfl --once
echo "result: $?"

debounce 的存在是因为大文件是逐步写入的:只有文件不再变化才处理, 因此不会读到写了一半的 PDF。

报告写为 <name>.report.json(或 .csv.html.pdf)。


pdfl fix

应用 fix:: 操作并保存新的 PDF。详见第 8 章

pdfl fix original.pdf normalize.pdfl --output out.pdf --dry-run  # 只看会做什么
pdfl fix original.pdf normalize.pdfl --output fixed.pdf          # 实际执行

pdfl inspect

无需脚本,快速查看 PDF 概要。

pdfl inspect <file.pdf>
File:     magazine.pdf
Size:     26 KB (27284713 bytes)
SHA-256:  af1029842e5bfeae338ead82fb449ef851be742b1d63117c12596e3ea123a616

Pages:    120
Page size: 496 x 709 pt
Boxes:    MediaBox, TrimBox, BleedBox

Metadata:
  Title: Example Magazine
  Creator: Adobe InDesign 19.3

Fonts:    26
  ABCDEF+Helvetica — embedded
  Arial — NOT embedded
Images:   81 (minimum DPI 136, spaces: DeviceCMYK, Indexed)
Max. estimated TAC: 300% (RGB render approximation)

Warnings:
  ! there are non-embedded fonts
  ! 3 image(s) below 300 DPI

新文件到达时第一个该运行的命令:几秒钟就能判断是否值得打开。


pdfl lint

不执行脚本,分析并报告质量问题。

pdfl lint <script.pdfl>

检出内容:

  • 声明后从未使用的变量、块参数和函数(加前缀 _ 可抑制:_page
  • 重复的 check
  • 未知的命名空间(text::struct::visual::prepress::codes::fix::data::
  • 位于 check 之外的 assert / require
  • 使用了 fix::(只能在 pdfl fix 中运行)
$ pdfl lint profile.pdfl
profile.pdfl: warning: variable 'LIMIT' declared and never used
profile.pdfl: warning: check "Fonts" declared 2 times

存在警告时退出码为 1,可用于 CI。


pdfl fmt

格式化脚本:两个空格缩进、统一空白、压缩空行。注释和单位(3mm 仍为 3mm) 都会保留。

pdfl fmt <script.pdfl>            # 原地格式化
pdfl fmt <script.pdfl> --check    # 不改动;未格式化时退出码 1
# 在 CI 中强制团队规范
for f in profiles/*.pdfl; do pdfl fmt "$f" --check || exit 1; done

pdfl doc

由脚本自身生成文档。

pdfl doc <script.pdfl> [--output markdown|html]

输出内容:配置名、常量表、函数、import,以及每个 check 的标签和校验内容 (assert 的消息成为说明)。

pdfl doc prepress.pdfl > docs/prepress-profile.md
pdfl doc prepress.pdfl --output html > profile.html

这是让不读代码的生产管理者了解配置在校验什么的交付物。


pdfl pack

把脚本和数据打包成可分发的 .pdflpkg

pdfl pack <folder> [--name <name>] [--version <version>] [--output <file>]

递归收集文件夹中的 .pdfl.csv.txt.json.xlsx,并附带记录了 各文件 SHA-256 的 manifest.json。打包是确定性的:同一文件夹生成完全相同的 字节。

pdfl pack profiles/print-shop --name print-profile --version 1.0.0

pdfl add

安装本地软件包,并校验清单中的哈希。

pdfl add print-profile.pdflpkg
# 安装到 ./pdfl_profiles/print-profile@1.0.0/

pdfl run pdfl_profiles/print-profile@1.0.0/prepress.pdfl file.pdf

若任一文件的哈希与记录不符,安装会被拒绝 — 损坏或被篡改的包不会进入。

远程仓库和数字签名不在本版本范围内:add 从本地文件安装。


pdfl fingerprint

本机指纹 —— 发给供应方以换取许可证的值。

pdfl fingerprint generate

指纹单独输出到标准输出,说明文字走标准错误,因此 pdfl fingerprint generate 2>/dev/null 得到的就是可直接管道传递的值。

$ pdfl fingerprint generate
Device fingerprint for this machine:

1a7553e711c53306be083ea8927c9cbe

Send it to your vendor to be issued a license.
It changes if the operating system is reinstalled.

指纹来自系统的机器标识符(/etc/machine-idIOPlatformUUIDMachineGuid)。重装操作系统后它会变,由同一镜像克隆的虚拟机共享同一个值。 对于每次运行都销毁机器的 CI 和容器,则有不看设备的 seatless 许可证。


pdfl license

校验收到的许可证。pdfl 只做校验:签发由供应方完成,所用的私钥不随二进制 文件分发。

pdfl license check <token>
$ pdfl license check "$(cat license.txt)"
signature:  ok
customer:   Example Print Shop
expires:    2027-01-01
features:   all
device:     1a7553e711c53306be083ea8927c9cbe
this machine: matches

许可证对本机有效时退出码为 0,否则为 2 —— 包括签名正确但许可证属于另一台 机器的情况,这时消息会明确说明。

选项 功能
--pubkey <base64> 使用指定公钥,而非编译时嵌入的那个

哪些命令需要许可证

runfixwatchcompareinspect 在本机没有有效许可证时以退出码 2 停止。 fingerprint generate 绝不需要——正是它产出申请许可证所需的值。lintfmtdocpack 只处理脚本、不碰 PDF,同样不需要。

令牌按以下顺序查找:

  1. $PDFL_LICENSE —— 面向 CI 的方式,在那里它作为 secret
  2. ~/.config/pdfl/license
  3. 可执行文件旁的 pdfl.license
$ pdfl run profile.pdfl file.pdf
error: no license found

Run 'pdfl fingerprint generate' and send the value to your vendor
to be issued a license. Then set PDFL_LICENSE, or save the token to
/home/you/.config/pdfl/license.

seatless 许可证有执行次数上限,记在 ~/.config/pdfl/state。同一个文件还记录 上次执行的时间,若时钟被回拨则拒绝运行——留有一小时容差,以免与时间同步冲突。


← 标准库 · 目录 · 下一章:实用范例 →