Perl:Getopt::Long 怎样区分缺参数、零值与业务错误
命令行工具的边界从参数开始。最低耗时 0 是合法筛选条件,缺少这个选项表示不筛选耗时;--min-duration -1 则违反业务规则。三种情况不能都用变量真假判断。参数解析器负责识别选项形状,应用负责定义值的含义,最后还要把失败落实到退出码和输出流。
本篇对应原定 25,使用新版 bin/logtool。原 bin/logtool.pl 的位置参数仍用于旧四字段实验。新入口采用 Getopt::Long,支持 import、query、report 三个子命令;--version 单独给出工具版本与实际解释器版本。命令例必须使用冻结解释器或明确标注本机差异,不能把环境中的第一个 perl 自动当成 5.38.2。
先选子命令,再解析它允许的选项
Getopt::Long 不会自动知道某个选项属于哪个子命令。本工具先取出命令名,为该命令构造允许的选项集合,之后调用 GetOptionsFromArray。import 允许 input、output、rejects;query 允许 input、level、min-duration、contains;report 允许 input、output。这使 query --output x 直接成为未知选项,而不是被解析成功后静默忽略。
1 | |
=s 要求提供字符串值。它使“选项存在但没有值”在解析阶段失败;它不会证明这个字符串是非负整数。使用字符串形式也让业务层能明确拒绝负数、前导零及超过九位的输入。Getopt 的自动缩写和忽略大小写均关闭,因此文档中的完整名称就是可依赖的接口,不需要猜测 --in 是否会在未来与新选项冲突。
require_order 使解析在第一个普通位置参数处停止,剩余参数随后被工具拒绝。这里要求所有业务输入都通过选项提供,没有额外位置参数。选项值含空格应由 shell 引号把它组织成一个参数;程序接到的是参数字符串,而不是引号字符本身。
[PATTERN] 限定每个操作的参数集合,再验证值。一个全局选项表接受所有参数,会把“合法但与本操作无关”变成隐藏的接口歧义。
合法零值要检查 defined
query 构造闭包前检查是否给出了最低耗时。没有给出时,不设置这个条件;给出了 0 时,转成数值零并传入过滤器。
1 | |
if ($minimum) 会把字符串零当假,误删调用方明确提供的筛选。五字段样本中有一条耗时缺失的 INFO;无条件查询返回四条,最低耗时 0 返回三条,因为 null 不是数值观测。这是零值反例的可见后果,不只是孤立的 Perl 真值表。
level 只能是 INFO、WARN、ERROR。contains 允许空串,它与缺失选项的调用含义不同,但对于已验证的消息字段,两者都会选中全部消息。闭包用 index 查询字面字符串;外部输入 .* 不作为正则执行,不需要调用方自己替程序转义。两个不同条件的闭包有各自词法变量,详见闭包专题。
[PATTERN] 可选参数用“是否存在”建立状态,再按域规则验证值。布尔真假只适合业务本来就需要真假的地方。
路径既有字符串身份,也有文件身份
report 的输出不能覆盖输入交换文件。两个字符串不同并不证明它们对应不同对象:相对路径、符号链接和硬链接可能指向同一文件。工具对已存在路径求 abs_path,再对输入与目标比较设备号和 inode;新文件则解析已存在父目录并拼接 basename。不存在父目录直接报错,避免到了写入阶段才产生模糊失败。
import 还要求接受文件、拒绝文件、输入三者路径不同。接受文件与拒绝文件已经存在时,硬链接也必须识别;这与仅比较字符串的实现不同。路径检查不是一个抵抗恶意并发改名的完整安全协议,课程操作对象是受控本地目录。具有对手的共享目录需要另外设计句柄和目录层面的访问策略,不能把这组检查扩大成一般防竞态保证。
带空格的目录是合法路径。shell 中用引号使它成为一个参数:
1 | |
该目录须预先存在。程序不为拼错的父目录自动创建一棵目录树。保存函数使用同目录临时文件,只有写入、close 都成功后才替换目标;输入解析失败不会先清空已有报表。
退出码和两个流共同表达结果
成功退出为 0,参数、数据和 I/O 错误退出为 2。成功 import 的 STDOUT 是三行计数;成功 query 是 JSONL,空查询是零字节;成功 report 是排序 TSV 或保存确认。诊断进入 STDERR,不能混进供下一阶段解析的 STDOUT。
这些业务错误有共同非零状态,但仍保留具体消息。程序启动前的解释器失败、系统发送的信号或 exec 失败不一定遵循业务层退出 2;重放记录原始退出状态,不能把所有非零值重新标成 2。工具显式检查 print,并在正常结束前检查 close STDOUT,因为缓冲写入错误可能只在 close 出现。SIGPIPE 在工具输出阶段被转换成可报告写入失败;外部强制 SIGKILL 仍不能被 Perl 捕获。
1 | |
最后两条 query 都必须非零,且不能误产生成功结果。重放同时检查退出码、STDERR 与关键 STDOUT 金标;报告哈希在同一记录中保存。帮助文本列出当前支持的参数,POD 中的例子也由实际导入、查询、报表命令覆盖,不能用“帮助可以打印”代替文档示例确实能运行。
练习:把 --min-duration 0 改成 --min-duration 00,为何不被解释成同一个数?课程限制输入的十进制写法,00 在业务验证阶段失败。另一题:参数已经解析成功,为什么仍要检查文件路径?参数解析只得到文本,无法证明对象存在、可读或与输出不同。
| 边界 | 验证位置 |
|---|---|
| 未知选项、缺值 | GetOptionsFromArray 返回值 |
| 零值、数值范围 | defined 与业务语法 |
| 路径别名 | 规范路径和 stat 身份 |
| 成功空查询 | 空 STDOUT、空 STDERR、退出 0 |
| 输出失败 | print 与显式 close |
参考:Perl 5.38.2 Getopt::Long、perlfunc 的 stat、close、print。运行源码与原始记录见 examples/perl-language/bin/logtool、tools/replay.pl 和补全证据目录。






