本系列的日志有三个 TAB 分隔符,第 4 列可以包含普通空格。用 split /\t/, $line, -1 得到四个字段;改成 split /\s+/, $line,消息 queued again 会再拆成两项,实测变成五个字段。正则捕获适合 16 的受限消息子语法,却不能替代整行的字段契约;substr 按固定位置取时间虽然短,也得先证明源格式确实固定且有足够长度。

perl examples/perl-language/experiments/16-20-patterns.pl 20 输出 tab-fields=4、whitespace-fields=5、fixed-prefix=short,STDERR 空、退出码 0。末行来自 substr('short', 0, 20):短输入并不会自动报“时间字段不够长”,却能返回 short。所以不能把无异常的 substr 当格式校验。正式工具先 split 成四列,再分别验证时间、等级和服务值;消息原样保留,没有通用 \s+ 裁剪。

1
2
3
整行 TSV —— 明确 TAB / 保留末空 ——> 四个字段
时间字段 —— 固定格式 + 日历校验 ——> 合法 UTC 字面值
消息字段 —— 可选受限模式匹配 ——> 子字段或拒绝

补充错误处理实验 course-semantics.pl eval_errors 把块 eval 和 $@ 分开记录:第一次 eval { die ... } 输出 eval_ok=0 first=1;随后一次成功的 eval { 1 } 输出 eval_success=1 after_success=empty saved=1。这说明 $@ 是当前 eval 的错误槽,成功后会清空;若需要保留上一处错误,必须立即复制到词法变量。

真实嵌套场景见 course-semantics.pl nested_eval:外层 eval 内先让主操作 die,立即保存 $main_error;随后 cleanup 的 eval { 1 } 成功并清空当前 $@;最后外层再 die。输出 nested_outer=0 main_saved=1 cleanup_slot=empty 与 outer_saved=1 main_still=1,证明 cleanup 成功不会让已复制的主错误丢失,外层错误也要另存。CLI 的 eval { ...; 1 } 错误出口只能读取当前层 $@;多阶段清理时要按阶段复制。

字段解析错误、正则子语法错误和子进程错误都可以进入同一个 CLI 退出码 2,但内部仍要先保存原始诊断,再决定对用户展示哪一层信息。不能在后续成功的 eval 之后再读取 $@ 推断旧错误,也不能把 warnings 当作 $@。官方依据是 Perl 5.38.2 perlfunc 的 eval 与 die,以及 perldiag 中异常和警告的区别。

练习:若消息是 queued<TAB>again,只对整行调用 split 能否知道这是额外字段还是消息里的 TAB?按现行协议,消息不允许 TAB,解析器应拒绝五列;要允许 TAB,必须先定义转义与反转义协议,再改写金标。正反输出与状态见 examples/perl-language/evidence/15-20.md;参考经 SHA-256 校验的 Perl 5.38.2 pod/perlfunc.pod(split、substr)和 pod/perlre.pod(The Basics)。