同一段汉字在源码里是一个字面量,在 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。

图:源码字节与运行时输入分别进入字符域。

1
2
源码文件 UTF-8 字节 --[use utf8 影响源码字面量]--> 字符串(2 个码点)
日志文件 UTF-8 字节 --[open <:raw,Encode::decode]--> 字符串(2 个码点)

LogTool::read_records 打开输入时声明 '<:raw',逐行调用 decode('UTF-8', $bytes, FB_CROAK | LEAVE_SRC)。use utf8 不能替代这里的显式解码;无效字节会在进入字段解析前被拒绝。反例由 t/09-10-unicode.t 构造:在合成行的消息尾放单个 0xFF,CLI 返回状态 2,STDOUT 空,STDERR line 1: invalid UTF-8。这证明工具拒绝该字节串,不保证所有源格式错误都能提供具体列号。

重复解码不是多一次保险

新版五字段入口仍然明确维护字节和字符两层。读取器拿到 raw 字节,解码一次;解析器、NFC 和查询接到字符;输出在最后编码一次。函数名也应使这条边界可辨识,不能把“字符串”当作足够完整的参数契约。Perl 标量可以保存字节内容或字符内容,调用方需要知道每个接口期待哪一类。

1
2
3
4
5
6
use Encode qw(encode decode FB_CROAK LEAVE_SRC);
use utf8;
my $bytes = encode('UTF-8', '中文');
my $characters = decode('UTF-8', $bytes, FB_CROAK | LEAVE_SRC);
my $second = eval { decode('UTF-8', $characters, FB_CROAK | LEAVE_SRC) };
die $@ if $@;

这段反例在第二次 decode 失败,原因是已经解码的宽字符不能当作 UTF-8 字节缓冲。ASCII 样本可能让重复解码看似无事,因为其字符和字节范围重叠;因此需要包含中文和非法字节的对照。不能据此宣称每一个重复解码样本都会报同样消息,低码点字符还可能产生别的错误或不符合意图的结果。t/course-core.t 对这组宽字符反例与正确往返分别断言。

FB_CROAK 使非法序列以异常终止;没有明确错误模式时的替换字符不是本课程选择的容错策略。LEAVE_SRC 则保留传入的原字节标量,避免检查过程消耗或改变证据。异常后立即取出 $@,不要在读取错误变量之前再调用可能执行 eval 的操作。消息转义的处理在解码之后进行,UTF-8 校验失败不能先猜测字段和业务错误。

[PATTERN] 在每个接口说明单位和转换方向:bytes → characters → bytes。使用非 ASCII 反例证明边界,避免只用范围重叠的输入建立错误信心。

练习:如果把 decode 删掉而只在脚本头加 use utf8,读入字节为何仍不能据此当作两个汉字?源码编译阶段与输入 I/O 阶段互不替代;读入时还需设置解码层或显式调用 decode。第二题:为什么不能先用替换字符继续处理,再把输入称为原样往返?非法字节已经丢失,输出无法恢复源数据。命令、输入十六进制、STDOUT/STDERR/退出码见 examples/perl-language/evidence/09-10.md 与补全证据。

接口 输入含义 输出含义
raw 文件读取 外部字节 字节标量
decode UTF-8 有效编码字节 字符标量
parse_line 已解码文本 字段记录
encode UTF-8 字符标量 输出字节

参考:经 SHA-256 校验的 Perl 5.38.2 官方 pod/perluniintro.pod(Perl’s Unicode Support、Handling Unicode)、pod/perlunicode.pod;Encode 3.19 Encode.pm 的 decode 与 LEAVE_SRC 文档。