Perl 11:空的末列不是不存在的列
日志最后一个消息为空时,原始行会以第三个制表符结束。如果只凭肉眼查看文件,...<TAB>api<TAB> 与缺少末列的 ...<TAB>api 很容易看混;解析器却不能把它们都解释成一条四字段记录。 LogTool::read_records 从原始字节按 LF 读一行,显式解码后只删除尾部的一个 \n,用 split /\t/, $line, -1 保留末尾空列,再检查恰好四项。demo.tsv 的第三行消息是合法空串,import 输出 IMPORTED<TAB>3<LF>;测试构造缺失第四列时,STDOUT 空,STDERR 为 line 1: expected four fields、退出码 2。额外的制表符也会触发同样拒绝。perlfunc 的 split 条目解释了 LIMIT 的效果,默认省略这个负 LIMIT 就不能直接依赖尾空列保持不变。 行终止符的决策也可复核:测试从合成的 ...<TAB>api<TAB> 输入去掉最后 LF,parse 仍接受并规范输出一个 LF;换成 C...
Perl 10:字符长度与输出字节要分别验收
解码后用 length 可以得到字符数,却不能据此推断写入文件的字节数。对于源码字面量 日志,字符数是 2,UTF-8 字节数是 6。报表或 JSON Lines 如果先编码一次,又经过另一层自动编码,文件内容就可能不等于金标;测试“屏幕上看起来正确”不足以排除这种错误。 Course 入口 examples/perl-language/bin/logtool 把 STDOUT 设为 :raw,在 report 命令里先构造 TSV 字符串,再用 Encode::encode('UTF-8', ..., FB_CROAK | LEAVE_SRC) 得到字节。course-semantics.pl unicode 的 decoded_chars=2 bytes=6 是最小机制断言;tools/replay.pl --case all 则走完整入口,记录 import、query、report 的 stdout SHA-256。两层证据分工不同:前者说明字符/字节边界,后者说明当前 CLI 输出字节与金标守恒。 图:输入解码、字符处理与输出编码 123raw 输入字节 --decod...
密码学 18:mTLS 与 TLS 终止后,客户端、代理和后端分别信谁
客户端通过 HTTPS 把合成订单发给服务,服务器不仅给出证书,还要求客户端也出示证书,握手成功,于是配置里写了“已启用 mTLS”。如果 TLS 实际终止在反向代理,而代理使用 HTTP 明文把订单送到后端,哪一段受 mTLS 保护?后端的业务程序究竟在信任客户端、代理,还是某个随请求转来的 X-Client-Name 字符串?把“开了 mTLS”写成“整个链路端到端机密、用户订单授权正确”并不严谨。 本篇用回环地址的三个真实组件验证第一层事实:客户端与代理做 TLS 1.3 双向证书认证,代理收到已验证客户端证书,后端却是另起的一条明文 HTTP 连接。没有安装服务网格,后端的订单也没有做真实权限检查;实验只支持连接段和证书验证对象的判断。 三个端点,两段连接 假设客户端拿着一张由测试 CA 签发、扩展用途为 clientAuth 的合成证书;代理有另外一张 serverAuth 的 orders.test 服务器证书。客户端的 TLS 验证器把测试 CA 明确加载到本次上下文并检查服务器名;代理单独加载这张测试 CA 并要求客户端证书。签发者的 CA 私钥只用于发两张短期测...
Perl 09:源码写成 UTF-8,不代表文件已经解码
同一段汉字在源码里是一个字面量,在 TSV 文件里却首先是一串字节。use utf8 管源码中如何解释非 ASCII 字面量和标识符;它不是给任何 open 自动加解码层。只有先区分这两条入口,length 才有可解释的单位。 perl -e 'print length("重试"), "\n"' 的 STDOUT 是 6,退出码 0;用 perl -Mutf8 -e 'print length("重试"), "\n"',输出是 2,退出码仍为 0。这组命令输入来自当前 UTF-8 shell;它测的是同一串源码字节在两种编译设置下的字面量长度,不是在测试从日志文件读入的解码配置。正式实验 experiments/09-10-encoding.pl 用 use utf8 声明源码字面量:characters=2;显式 encode('UTF-8', ...) 得 input-bytes=6,再 decode 后 decoded-characters=2。 图:源码字节与运行时输入分别进入字符域。 12源...
Perl 08:返回“字段列表”会让调用位置改写结果
“解析出 api 和 queued 两个字段”若用 return ('api', 'queued') 表达,my @fields = ... 能得到两个值,my $field = ... 却只得到末项 queued。使用者不是换了函数,只换了赋值位置,结果形状已经变了。perlsub 对返回表达式的说明明确指出,它在调用者要求的上下文中求值;perldata 再说明标量位置的列表字面值为何取最后一项。不能把它误说成“数组在标量位置也取最后一项”:数组会给长度。 实验 examples/perl-language/experiments/08-return.pl 的金标是 list=api|queued、scalar=queued、ref=ARRAY、count=2;命令、无 STDERR 和退出码 0 记录在 examples/perl-language/evidence/05-08.md。前两行是一个易用错的反例,后两行用 return ['api','queued'] 将单个引用作为返回契约:接收端须显式解引用才取字段。这是当前 LogTool::read_records 选...
Perl 07:给 $_[0] 赋值可能改掉调用者
数据规范化需要把一个服务名转成另一种写法,但把“返回规范值”误写成“修改第一个参数”,调用者原先的记录就被污染了。在没有 signatures 的传统子程序里,@_ 的元素可能是调用参数的别名;这不等同于一个完全独立的形参数组。 examples/perl-language/experiments/07-arguments.pl 用一个可写的变量调用 mutate_argument($service),子程序写 $_[0] = 'worker'。实测 STDOUT 第一行 after-alias=worker;第二个函数先用 my ($service) = @_ 接收值,再把局部变量改成 cache,返回 local=cache,外面的值仍是 after-copy=worker。该演示只展示了可写标量变量作为实参的情况;如果传不可写常量,修改可能直接报错。局部复制也不是深拷贝:实参若是可变对象的引用,复制后的引用仍能改动同一目标。 因此日志工具把调用契约写到数据结构边界:format_record($record) 接收记录引用,只读取固定字段;read_records($pat...
Perl 06:读取缺失的嵌套键,也可能修改输入
解析器接到 {} 后,写 $tree->{missing}{count} 看似只是问一个数字。可是要访问第二层,Perl 会把第一层的 missing 自动创建为哈希引用。若随后把这棵树当作原始记录转交,单纯的“探测”已经修改了输入。这个例子只说明这一种解引用路径,不声称所有 exists、赋值和类型组合都会以同一方式自动创建。 运行 perl examples/perl-language/experiments/06-autovivify.pl,STDOUT 依次为 before=0、after-read=1、created=HASH,此时读取的 $value 仍未定义。脚本随后对另一份空树先检查 exists $safe->{missing},显式拒绝,STDERR 是 missing field,退出码 255。这段反例的失败不是编译器自动给出数据契约,而是读代码的人主动定义了缺键不可接受的规则。测试 t/05-08-boundaries.t 同时断言了意外创建和拒绝诊断。 在累计工具里,外部日志只在读入后被构造成四个明确字段:time、level、servic...
Perl 05:引用决定谁能改动同一份记录
03 的字符串数组复制没有改变原数组,但解析器里的一条日志是哈希引用。如果另一个阶段拿到的是同一引用,它改变记录的字段,后续报表会看到修改后的值。是否“复制成功”要看复制的是标量内容、容器,还是容器里的引用。 examples/perl-language/experiments/05-references.pl 做了两组对照。\@source 指向现有数组,[@source] 新建数组;两者初始内容相同。向前者追加 worker 后,原数组输出 source=api|worker;向新数组追加 cache,其输出 copy=api|cache,原数组并未得到 cache。随后把同一个哈希引用放入新数组,改其 label:原哈希输出 shared=changed。这并非矛盾:数组外壳是新建的,元素里保存的引用仍指向同一哈希。 图:数组外壳复制与元素引用共享 123@source ---- \@source (同一个数组) | 取元素值 +---------> [@source] (新数组;若元素是引用,其目标未复制) 命令 perl examples/per...
Perl 04:没有字段、空消息和数字零不是同一件事
解析器若写 die unless $message,会拒绝空消息,也会拒绝文本 0;但本系列 TSV 允许空消息。perldata 的 Scalar values 指出:undef、空串和零在布尔判断里都为假;前者还可以用 defined 单独区分。字段是否存在,不能靠真假值来推断。 执行 perl examples/perl-language/experiments/04-values.pl,五种值按 undef、''、'0'、0、'queued' 排列。STDOUT 为: 1234560:defined=0,truth=01:defined=1,truth=02:defined=1,truth=03:defined=1,truth=04:defined=1,truth=1empty-message-accepted STDERR 空、退出码 0。用 defined 只检查是否提供值,不限制内容;如果业务要求消息非空,还须另加 length 条件。本系列的 demo.tsv 第三行有第四列,却在该列放空串:split /\t/, $line, -1 特意保留尾部空字段,所以 i...
Perl 03:列表是值,数组才保存元素
日志服务名 ('api', 'worker') 是一次表达式产生的列表值;@services 是保存元素、可供后续 push 的数组变量。二者看起来都写成一串值,但在标量位置并不相同。Perl 5.38.2 perldata 的 List value constructors 明确区分:标量位置的列表字面值取最后一个元素;数组在标量位置给长度。拿前一种规则推断后一种,会把“两个服务”误看成服务 worker。 examples/perl-language/experiments/03-arrays.pl 从两个名字创建数组、复制到 @copy,只给副本追加 cache,再从原数组取切片。执行命令 perl examples/perl-language/experiments/03-arrays.pl:STDOUT 是 source=api|worker、copy=api|worker|cache、slice=api|worker、length=2 四行;STDERR 空、退出码 0。@copy = @services 在这里复制了字符串元素,所以追加副本不修改原数组。这个观察不能...






![Perl 07:给 $_[0] 赋值可能改掉调用者](/img/wall-paper-108.jpg)



