几个小时以来,我一直在与我的 Perl 程序中的一个错误作斗争。我不确定是我做错了什么还是解释器做错了,但是代码是非确定性的,而它应该是确定性的,IMO。它还在古老的 Debian Lenny (Perl 5.10.0) 和刚刚升级到 Debian Wheezy (Perl 5.14.2) 的服务器上表现出相同的行为。它归结为这段 Perl 代码:
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";
my $c = "";
open C, ">:utf8", \$c;
print C "š";
close C;
die "Does not happen\n" if utf8::is_utf8($c);
print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";
它以启用警告的严格模式初始化 Perl 5 解释器,使用字符串(而不是字节字符串)和以 UTF8 编码的命名标准流(UTF-8 的内部概念,但非常接近;更改为完整的 UTF-8 没有区别)。然后它打开一个“内存文件”(标量变量)的文件句柄,在其中打印一个单字节 UTF-8 字符并在关闭时检查该变量。
标量变量现在总是关闭 UTF8 位。然而,它有时包含一个字节字符串(通过 转换为字符串utf8::decode()
),有时包含一个只需要在其 UTF8 位 ( Encode::_utf8_on()
) 上翻转的字符串。
当我重复执行我的代码(1000 次,通过 Bash)时,它会Undecoded
以Decoded
大致相同的频率打印。当我更改写入“文件”的字符串时,例如在其末尾添加一个换行符,就会Undecoded
消失。当utf8::decode
成功并且我在循环中尝试相同的原始字符串时,它会在同一个解释器实例中保持成功;但是,如果失败,它会继续失败。
观察到的行为的解释是什么?如何将标量变量的文件句柄与字符串一起使用?
巴什游乐场:
for i in {1..1000}; do perl -we 'use strict; use utf8; binmode STDOUT, ":utf8"; binmode STDERR, ":utf8"; my $c = ""; open C, ">:utf8", \$c; print C "š"; close C; die "Does not happen\n" if utf8::is_utf8($c); print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";'; done | grep Undecoded | wc -l
作为参考和绝对肯定,我还制作了一个带有迂腐错误处理的版本——结果相同。
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
binmode STDOUT, ":utf8" or die "Cannot binmode STDOUT\n";
binmode STDERR, ":utf8" or die "Cannot binmode STDERR\n";
my $c = "";
open C, ">:utf8", \$c or die "Cannot open: $!\n";
print C "š" or die "Cannot print: $!\n";
close C or die "Cannot close: $!\n";
die "Does not happen\n" if utf8::is_utf8($c);
print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";