Perl 30:分号在参数里不该变成第二条命令
工具的 probe FILE TOKEN 先验证合成输入,再用当前 $^X 启动固定的 Perl 子脚本,传入一个 1–24 字节可打印 ASCII token。调用 probe demo.tsv 'alpha;echo BAD' 时,子进程得到的是完整 $ARGV[0];实际 STDOUT arg=alpha;echo BAD\n,STDERR probe note\n,退出 0。没有第二个 echo 命令执行。这个断言验证此处的参数列表调用,不等于证明任意字符串拼装的 shell 命令安全。 12open3(undef, $child_stdout, $child_stderr, $program, '-e', $child_code, '--', $token); -- 将 token 留在 Perl 脚本的参数区;CLI 不接受用户指定 $program 或 $child_code,这两个值分别是 $^X 与固定源码。反例输入含实际换行,CLI 在创建子进程前以 probe requires one ASCII token o...
Perl 29:错误与空查询不能共用一个返回值
查 demo.tsv 里的 ERROR 等级没有匹配,是完全有效的结果:退出 0、STDOUT 和 STDERR 均 0 字节。把输入改为 invalid.tsv,查询同样没有任何记录可发布,却必须是退出 2、STDOUT 空、STDERR line 1: invalid record\n。如果调用者只用 defined($output) 或只看文件长度,两个场景无法区分。 当前 CLI 将 open 失败、读失败、坏记录、子进程状态分别生成文字诊断,通过最外层 eval 统一映射为 CLI 状态 2;read_records 抛错时既不返回部分记录,也不输出部分查询。这个 2 是本工具的应用层约定,不等于 Perl 的 $!、子进程 $? 或操作系统 errno。报错中行号只在解析阶段有意义,不能把目录的读失败硬说成“第 1 行无效”。 图:空查询、格式错误与读取错误的三通道结果。 123正常读完 + 0 条匹配 -> status 0, stdout "", stderr ""字段损坏 -> statu...
Perl 28:读到 undef 还不能宣告输入结束
对三行样本执行 import,读完、解析、关闭后输出 IMPORTED\t3\n。但“打开成功”不保证“读取成功”:在本机 Linux,open my $input, '<:raw', '.../fixtures' 对目录可成功,第一次 <$input> 随后返回 undef,$! 是 Is a directory。原来的 while (<$input>) 把这一回当 EOF,甚至可能输出 IMPORTED\t0。这不是解析错误,而是 I/O 错误被错误地翻译为有效空文件。 现在逐次 readline 前清 $!;若返回 undef,先检查 $!,有错误则停止并诊断,只有没有错误才视为 EOF。读完仍检查 close,不能将关闭失败当作成功。针对目录的反例实测 STDOUT 0 字节、STDERR cannot read input: Is a directory\n、退出 2;普通合成文件正例返回 0、STDERR 0 字节。目录的这一行为只在冻结 Linux 环境测过,别的平台不能由此推定。 123456$! = 0;my $bytes = &l...
Perl 27:对象的散列副本不是访问控制
上一章的构造器验证等级,若调用者随后把原散列的 INFO 改成 ERROR,报表所见的对象是否跟着变化?new 创建了新的最外层散列,因此不会。测试先构造 INFO,再修改 $fields->{level},$record->level 仍是 INFO;as_hash 又返回 { %$self },修改返回的散列最外层字段同样不改对象。这是两处明确的浅复制,不是 Perl 自动的对象快照。 图:构造复制与对象数据所有权 123调用者 hash ----(逐字段复制)----> bless 后的 hash | | +-- 修改 level 不传过去 +-- as_hash 另复制一层 反例更关键:$record->{level} = 'WARN' 可以直接改对象内部,之后 level 返回 WARN。bless 没有隐藏散列槽位,也没实施只读约束。本例四个值都是字符串,因此上述浅复制足够描述当前结构;将来若 message 改为嵌套引用,内层仍会共享,需重新制定所...
Perl 26:bless 不替你验证日志记录
前 25 篇用散列引用传递已经校验过的记录。报表若需要一个对象接口,最先要问的不是怎么写 bless,而是传给构造器的四个字段究竟由谁验证:bless 只把引用关联到一个包,不检查散列结构,更不会把 DEBUG 变成允许的日志等级。 LogTool::Record->new($fields) 首先要求普通散列引用,time、level、service、message 四个键都存在且已定义,再限制 level 为 INFO、WARN 或 ERROR。这里的 message => '' 合法;删掉 message 或换成 undef 才是不满足构造契约。执行构造 INFO 的正例,STDOUT 为 INFO\n、STDERR 空、退出 0;把等级换成 DEBUG,STDOUT 空,STDERR 为 invalid record level\n,裸 perl -e 退出 255。完整命令与金标见 examples/perl-language/evidence/26-31.md。 12my $record = LogTool::Record->new($fields);...
Perl 25:prove 应能报告故障,不只是打印 PASS
一份测试若总在成功路径上说 ok,却从未检查过拒绝时的错误流,破损解析器也可能继续通过。当前 t/ 目录覆盖核心输入、上下文、引用、Unicode、正则、模块版本、隔离 CSV 和帮助命令,调用真实 Perl 子进程分别比较退出码、STDOUT、STDERR;一条用错码位的断言还需要让测试框架明确失败。 2026-10-06 历史增量运行 prove -r examples/perl-language/t,当时报告 7 个文件、105 个断言,Result: PASS,命令退出 0,STDERR 空。反例 fixtures/deliberate-failure.t 故意要求 is 2, 3;单独运行 prove -v examples/perl-language/fixtures/deliberate-failure.t,STDOUT 中有 not ok 1、Result: FAIL,STDERR 标明 got 2、expected 3,退出码 1。该文件不放进默认的 t/:它是验收 TAP 失败通道的金标,不是被忽略的回归失败。 图:执行结果进入断言,再由 harness 汇总...
Perl 24:让 --help 从工具自身的 POD 解释失败状态
脚本长到有五个子命令后,光给出一串参数名不够。调用者需要知道 UTF-8 输入的四列、空结果状态、服务名规范化和报表是否写文件;如果帮助只说明“按日志统计”,运维程序可能把空查询与解析失败当成一回事。 CLI 在 __END__ 后保留 NAME、SYNOPSIS、DESCRIPTION 三段 POD;--help 由随 Perl 安装的 Pod::Usage 2.03 读取同一文件生成文本。实际运行 perl examples/perl-language/bin/logtool.pl --help:STDOUT 从 NAME 开始并列出 report-csv FILE 与空集的状态 0 等契约,STDERR 空、退出码 0。起初直接要求 verbose 2 时,环境里的 perldoc 命令提示没有安装 perl-doc 包,虽有输出却污染 STDERR;改为 -noperldoc => 1 后用内置解析器生成同一份 POD,不靠关闭警告假装无故障。 反例 --help unexpected 不会静默忽略多余参数:STDOUT 空,累计版本的 STDERR 是 usage...
Perl 23:把 CPAN 安装放进实验树,而不是系统 Perl
报表在 14 已有确定顺序的 TSV。要给接收 CSV 的程序另一种表示,需要的是正确处理引号和逗号的编码器,不能简单把制表符换成逗号。这里为同一 CLI 加 report-csv,但不让安装实验破坏系统 Perl 的模块集合。 Text-CSV-2.06.tar.gz 由 CPAN 发布目录下载;发布者 CHECKSUMS 对该文件的 SHA-256 是 dfcaec925a788b0ba41e51bc6d16e21b0e98b4c7af9b79395090add75f5e506f,本机下载值相同。先验证系统中 perl -MText::CSV 找不到模块,再在临时源码目录运行 perl Makefile.PL INSTALL_BASE=.../examples/perl-language/vendor、make test、make install。实际安装的 Text::CSV 和内附 Text::CSV_PP 都为 2.06;vendor/Text-CSV-2.06.tar.gz 保留上游元数据与许可证说明,安装后的两个模块也随实验包交付。没有往 /usr 安装依赖。 ME...
Perl 22:版本检查应指向实际装载的模块
语言是 5.38.2,不表示每个模块都叫 5.38.2。文件编码用到本机的 Encode 3.19,UTC 日期校验依赖 Time::Local 1.30,POD 帮助用到 Pod::Usage 2.03;它们有各自版本与装配路径。误把 perl -v 当成模块版本锁,安装相同 Perl patch 的机器也可能得到不同模块行为。 自己的 LogTool 设为 0.100。perl -Iexamples/perl-language/lib -e 'use LogTool 0.100; print LogTool->VERSION, "\n";' 输出 0.100、STDERR 空、退出码 0;把要求改成 99,STDOUT 空、STDERR LogTool version 99 required--this is only version 0.100 ...、退出码 255。版本门槛在加载时生效;它只说明某个模块声明的版本不低于要求,不能证明所有输入都能通过或整个依赖图一致。 examples/perl-language/VERSIONS.md 列出 0...
Perl 21:把日志解析搬进模块,不等于导出所有名字
此前 CLI 直接执行 LogTool::read_records 等过程。模块文件位于 examples/perl-language/lib/LogTool.pm,包名为 LogTool,函数是包里的名字;在调用者写 use LogTool ... 时,装载模块和把名字导入调用者的命名空间又是两步。把“能装载文件”误解为“任意函数都会自动出现”,会让调用代码依赖不透明的导出习惯。 当前模块用 strict、warnings,设 $LogTool::VERSION = '0.100',只在 @EXPORT_OK 列出 read_records、format_record、report、report_rows。bin/logtool.pl 通过 FindBin 指向自己的 ../lib 再显式导入这四个函数。测试先调用 report_rows([]),得到结构明确的 [['TOTAL', 0]];反例执行 perl -Iexamples/perl-language/lib -e 'use LogTool qw(not_exported);',STDOUT 空,STDERR 指明该名...










