Perl: utf8::decode vs. Encode::decode

8

我正在尝试区分使用Encode::decode("utf8", $var)utf8::decode($var)之间的差异,结果很有趣。我已经发现多次调用前者会导致错误“Cannot decode string with wide characters at...”,而后者方法将愉快地运行多次,只返回false。

我遇到的问题是如何理解length函数根据使用不同的解码方法返回不同的结果。这个问题的原因是我正在处理来自外部文件的“双重编码”utf8文本。为了演示这个问题,我创建了一个名为“test.txt”的文本文件,在一行上放置了以下Unicode字符:U+00e8、U+00ab、U+0086、U+000a。这些Unicode字符是Unicode字符U+8acb的双重编码,以及一个换行符。该文件被编码为UTF8格式存储在磁盘上。然后我运行以下perl脚本:

#!/usr/bin/perl                                                                                                                                          
use strict;
use warnings;
require "Encode.pm";
require "utf8.pm";

open FILE, "test.txt" or die $!;
my @lines = <FILE>;
my $test =  $lines[0];

print "Length: " . (length $test) . "\n";
print "utf8 flag: " . utf8::is_utf8($test) . "\n";
my @unicode = (unpack('U*', $test));
print "Unicode:\n@unicode\n";
my @hex = (unpack('H*', $test));
print "Hex:\n@hex\n";

print "==============\n";

$test = Encode::decode("utf8", $test);
print "Length: " . (length $test) . "\n";
print "utf8 flag: " . utf8::is_utf8($test) . "\n";
@unicode = (unpack('U*', $test));
print "Unicode:\n@unicode\n";
@hex = (unpack('H*', $test));
print "Hex:\n@hex\n";

print "==============\n";

$test = Encode::decode("utf8", $test);
print "Length: " . (length $test) . "\n";
print "utf8 flag: " . utf8::is_utf8($test) . "\n";
@unicode = (unpack('U*', $test));
print "Unicode:\n@unicode\n";
@hex = (unpack('H*', $test));

print "Hex:\n@hex\n";

这将产生以下输出:
长度:7
utf8标志:
Unicode:
195 168 194 171 194 139 10
十六进制:
c3a8c2abc28b0a
==============
长度:4
utf8标志:1
Unicode:
232 171 139 10
十六进制:
c3a8c2abc28b0a
==============
长度:2
utf8标志:1
Unicode:
35531 10
十六进制:
e8ab8b0a
这是我所期望的。长度最初为7,因为perl认为$test只是一系列字节。解码一次后,perl知道$test是一系列以utf8编码的字符(即使$test在内存中仍然是7个字节,perl返回4个字符的长度而不是7个字节)。第二次解码后,$test包含4个字节,被解释为2个字符,这正是我所期望的,因为Encode::decode将4个代码点解释为utf8编码的字节,结果是2个字符。奇怪的是,当我修改代码调用utf8::decode时(将所有$test = Encode::decode("utf8", $test);替换为utf8::decode($test))。
这几乎产生相同的输出,只有长度的结果不同:
长度:7
utf8标志:
Unicode:
195 168 194 171 194 139 10
十六进制:
c3a8c2abc28b0a
==============
长度:4
utf8标志:1
Unicode:
232 171 139 10
十六进制:
c3a8c2abc28b0a
==============
长度:4
utf8标志:1
Unicode:
35531 10
十六进制:
e8ab8b0a
看起来perl首先计算解码前的字节数(如预期),然后在第一次解码后计算字符数,但在第二次解码后再次计算字节数(这不是预期的)。为什么会发生这种转换?我对这些解码函数的工作方式理解有误吗?
谢谢, Matt

1
你为什么要使用 require 而不是 use 来引入模块? - Eric Strom
1
我没有<code>使用</code>utf8,因为这会告诉Perl代码本身是以utf8编码的,而我不需要(http://perldoc.perl.org/utf8.html)。我想我本可以使用Encode,但我只是碰巧没用。 - Matt
2个回答

4

您不应该使用utf8 pragma模块中的函数。其文档如此规定:

不要将此pragma用于除告诉Perl您的脚本是用UTF-8编写之外的任何其他事情。

始终使用Encode模块,并查看问题{{link3:使用Perl进行Unicode编码的清单}}。 unpack太低级了,它甚至不能为您提供错误检查。

您的假设是错误的,即字节E8 AB 86 0A是UTF-8双重编码字符newline的结果。这是这些字符的单个UTF-8编码的表示。也许您的整个混淆都源于这个错误。

length被不适当地重载,在某些时候它确定字符长度或八进制长度。使用更好的工具,例如Devel::Peek

#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use Devel::Peek qw(Dump);
use Encode qw(decode);

my $test = "\x{00e8}\x{00ab}\x{0086}\x{000a}";
# or read the octets without implicit decoding from a file, does not matter

Dump $test;
#  FLAGS = (PADMY,POK,pPOK)
#  PV = 0x8d8520 "\350\253\206\n"\0

$test = decode('UTF-8', $test, Encode::FB_CROAK);
Dump $test;
#  FLAGS = (PADMY,POK,pPOK,UTF8)
#  PV = 0xc02850 "\350\253\206\n"\0 [UTF8 "\x{8ac6}\n"]

2
感谢您的回复。Perl文档确实说可以使用utf8模块中的函数。在引用后面的句子是“The utility functions described below are directly usable without use utf8;”,也就是说,如果不需要,就不应该使用(perl关键字use)utf8编译指示,但可以使用(英语use)它的函数。此外,我意识到“eaab860a”是单一编码。我的文件包含了“c3a8c2abc28b0a”的八位组,这是双重编码。事实证明,我的困惑源于“length”函数中的一个错误。请参见http://www.perlmonks.org/?node_id=874996。 - Matt
10
实际上它的意思是“除了告诉Perl你的脚本使用UTF-8编写外,不要将此Pragma用于其他任何目的。下面描述的实用函数可以直接使用而无需使用utf8;。”,这明显不意味着“你不能使用utf8 pragma模块中的函数”。它的意思是你不需要使用pragma来导入这些函数。 - user181548


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接