perlunifaq - Perl Unicode 常见问题解答
这是一份关于 Perl 中 Unicode 的问答列表,建议在阅读 perlunitut 后阅读。
不,这也不是真正的 Unicode 常见问题解答。
Perl 对所有支持的字符编码都有一个抽象接口,所以这实际上是一个通用的 Encode
教程和 Encode
常见问题解答。但许多人认为 Unicode 是特殊的,有魔力的,我不想让他们失望,所以我决定把这份文档称为 Unicode 教程。
要找出你的 Perl 支持哪些字符编码,请运行
perl -MEncode -le "print for Encode->encodings(':all')"
好吧,如果你可以,升级到最新版本,但至少要升级到 5.8.1
或更新版本。本教程和常见问题解答假设使用最新版本。
你应该也检查你的模块,并在必要时升级它们。例如,HTML::Entities 需要版本 >= 1.32 才能正常工作,即使变更日志对此保持沉默。
好吧,除了一个简单的 binmode $fh
之外,你不应该对它们进行特殊处理。(需要 binmode,因为否则 Perl 可能会在 Win32 系统上转换行尾。)
但要小心,永远不要将文本字符串与二进制字符串混合使用。如果你需要在二进制流中使用文本,请先使用适当的编码对你的文本字符串进行编码,然后将它们与二进制字符串连接起来。另请参阅:“如果我不编码怎么办?”。
无论何时您要与 Perl 进程外部的任何内容(如数据库、文本文件、套接字或其他程序)进行文本通信,即使您通信的对象也是用 Perl 编写的,也需要进行编码和解码。
当您的编码二进制字符串与文本字符串一起使用时,Perl 会假设您的二进制字符串使用 ISO-8859-1(也称为 latin-1)编码。如果它不是 latin-1,那么您的数据将被不愉快地转换。例如,如果它是 UTF-8,则多字节字符的各个字节将被视为单独的字符,然后再次转换为 UTF-8。这种双重编码可以比作双重 HTML 编码(>
)或双重 URI 编码(%253E
)。
这种静默的隐式解码被称为“升级”。这听起来可能很积极,但最好避免它。
这取决于您输出的内容以及输出方式。
如果字符串的所有字符都为代码点 255 或更低,Perl 将输出与这些代码点匹配的字节。这就是编码字符串发生的情况。但是,它也可以发生在恰好所有代码点都为 255 或更低的未编码字符串上。
否则,Perl 将输出以 UTF-8 编码的字符串。这只会发生在您忘记编码的字符串上。由于这种情况不应该发生,因此 Perl 在这种情况下还会抛出“宽字符”警告。
exec
、chdir
等)您的文本字符串将使用 Perl 内部格式中的字节发送。
由于内部格式通常是 UTF-8,因此这些错误很难发现,因为 UTF-8 通常是您想要的编码!但不要偷懒,不要利用 Perl 内部格式是 UTF-8 的事实。显式编码以避免奇怪的错误,并向维护程序员表明您已经考虑过这个问题。
如果来自某个句柄的所有数据都以完全相同的方式编码,您可以使用 encoding
层告诉 PerlIO 系统自动解码所有内容。如果您这样做,您就不必再在使用分层句柄的事物上忘记解码或编码。
您可以在open
文件时提供此层
open my $fh, '>:encoding(UTF-8)', $filename; # auto encoding on write
open my $fh, '<:encoding(UTF-8)', $filename; # auto decoding on read
或者,如果您已经有一个打开的文件句柄
binmode $fh, ':encoding(UTF-8)';
一些 DBI 的数据库驱动程序也可以自动进行编码和解码,但这有时仅限于 UTF-8 编码。
尽你所能找出,如果必须:猜测。(不要忘记用注释记录你的猜测。)
您可以在 Web 浏览器中打开文档,并更改字符集或字符编码,直到您可以直观地确认所有字符都按预期显示。
无法可靠地自动检测编码,因此,如果人们继续向您发送没有字符集指示的数据,您可能需要教育他们。
是的,可以!如果您的源代码是 UTF-8 编码的,您可以使用use utf8
pragma 来指示这一点。
use utf8;
这不会对您的输入或输出做任何事情。它只影响读取源代码的方式。您可以在字符串文字、标识符(但它们仍然必须是根据\w
的“单词字符”)甚至自定义分隔符中使用 Unicode。
不,Data::Dumper 的 Unicode 功能应该如此。有些人抱怨它应该在使用eval
再次读取数据时恢复 UTF8 标志。但是,您实际上不应该查看该标志,并且没有任何迹象表明 Data::Dumper 应该违反此规则。
以下是发生的情况:当 Perl 读取字符串文字时,它会尽可能地坚持使用 8 位编码。(但也许最初它是以 UTF-8 内部编码的,当您将其转储时。)当它必须放弃这一点,因为其他字符被添加到文本字符串中时,它会静默地将字符串升级到 UTF-8。
如果您正确地对字符串进行编码以进行输出,那么这一切都不关您的事,您可以像往常一样eval
转储数据。
从 Perl 5.14(以及部分在 Perl 5.12 中)开始,只需在程序开头附近添加 use feature 'unicode_strings'
。在其词法作用域内,您不应该遇到此问题。它也会在 use feature ':5.12'
或 use v5.12
下自动启用,或者在 Perl 5.12 或更高版本中使用命令行上的 -E
启用。
需要此功能的原因是为了不破坏依赖于 Unicode 出现之前工作方式的旧程序。这些旧程序只知道 ASCII 字符集,因此可能无法正确处理其他字符。当字符串以 UTF-8 编码时,Perl 假设程序已准备好处理 Unicode,但当字符串未以 UTF-8 编码时,Perl 假设只希望 ASCII,因此那些不是 ASCII 字符的字符不会被识别为它们在 Unicode 中是什么。use feature 'unicode_strings'
告诉 Perl 将所有字符视为 Unicode,无论字符串是否以 UTF-8 编码,从而避免了这个问题。
但是,在较早的 Perl 版本中,或者如果您将字符串传递给特征作用域之外的子例程,您可以通过将编码更改为 UTF-8 来强制使用 Unicode 规则,方法是执行 utf8::upgrade($string)
。这可以在任何字符串上安全使用,因为它会检查并不会更改已经升级的字符串。
有关更详细的讨论,请参阅 CPAN 上的 Unicode::Semantics。
请参阅上一个问题的答案。
您不能。有些人使用 UTF8 标志来实现这一点,但这是一种误用,并且会让像 Data::Dumper 这样的行为良好的模块看起来很糟糕。该标志对于此目的毫无用处,因为它在使用 8 位编码(默认情况下为 ISO-8859-1)存储字符串时处于关闭状态。
这是您作为程序员需要跟踪的事情;抱歉。您可以考虑采用某种“匈牙利命名法”来帮助解决这个问题。
首先将FOO编码的字节字符串转换为文本字符串,然后将文本字符串转换为BAR编码的字节字符串
my $text_string = decode('FOO', $foo_string);
my $bar_string = encode('BAR', $text_string);
或者跳过文本字符串部分,直接从一种二进制编码转换为另一种
use Encode qw(from_to);
from_to($string, 'FOO', 'BAR'); # changes contents of $string
或者让自动解码和编码完成所有工作
open my $foofh, '<:encoding(FOO)', 'example.foo.txt';
open my $barfh, '>:encoding(BAR)', 'example.bar.txt';
print { $barfh } $_ while <$foofh>;
decode_utf8
和encode_utf8
?这些是decode('utf8', ...)
和encode('utf8', ...)
的替代语法。不要将这些函数用于数据交换。请改用decode('UTF-8', ...)
和encode('UTF-8', ...)
;请参见下面的"UTF-8和utf8有什么区别?"。
这是一个用于占用多个字节的字符的术语。
Perl 警告“Wide character in …”是由此类字符引起的。在没有指定编码层的情况下,Perl 尝试将内容放入单个字节中。如果无法做到,它会发出此警告(如果启用了警告),并使用 UTF-8 编码的数据。
为了避免此警告并避免在单个流中出现不同的输出编码,请始终显式指定编码,例如使用 PerlIO 层
binmode STDOUT, ":encoding(UTF-8)";
请不要考虑 UTF8 标志,除非您正在修改内部机制或调试奇怪的行为。这意味着您很可能不应该使用is_utf8
、_utf8_on
或_utf8_off
。
UTF8 标志,也称为 SvUTF8,是一个内部标志,指示当前内部表示形式为 UTF-8。如果没有此标志,则假定为 ISO-8859-1。Perl 会自动在它们之间进行转换。(实际上,Perl 通常假设表示形式为 ASCII;请参见上面的"为什么正则表达式字符类有时只匹配 ASCII 范围内的字符?"。)
Perl 的内部格式之一恰好是 UTF-8。不幸的是,Perl 无法保守秘密,所以每个人都知道这一点。这就是造成很多困惑的原因。最好假装内部格式是某种未知编码,并且你始终需要显式地编码和解码。
use bytes
编译指示怎么样?不要使用它。在文本字符串中处理字节没有意义,在字节字符串中处理字符也没有意义。进行适当的转换(通过解码/编码),一切都会顺利进行:你将获得解码数据的字符计数,以及编码数据的字节计数。
use bytes
通常是试图做一些有用的事情的失败尝试。忘记它吧。
use encoding
编译指示怎么样?不要使用它。不幸的是,它假设程序员的环境和用户的环境将使用相同的编码。它将对源代码以及 STDIN 和 STDOUT 使用相同的编码。当程序被复制到另一台机器时,源代码不会改变,但 STDIO 环境可能会改变。
如果你需要在源代码中使用非 ASCII 字符,请将其设置为 UTF-8 编码文件并使用 use utf8
。
如果你需要设置 STDIN、STDOUT 和 STDERR 的编码,例如基于用户的区域设置,请使用 use open
。
:encoding
和 :utf8
有什么区别?由于 UTF-8 是 Perl 的内部格式之一,你通常可以跳过编码或解码步骤,直接操作 UTF8 标志。
与 :encoding(UTF-8)
相比,你可以简单地使用 :utf8
,如果数据在内部已经表示为 UTF8,则跳过编码步骤。这在写入时被广泛认为是良好的行为,但在读取时可能很危险,因为它会导致内部不一致,因为你可能拥有无效的字节序列。在输入时使用 :utf8
有时会导致安全漏洞,因此请改用 :encoding(UTF-8)
。
除了decode
和 encode
,你也可以使用_utf8_on
和 _utf8_off
,但这种方式被认为是糟糕的风格。特别是_utf8_on
可能会很危险,原因和:utf8
一样。
对于单行代码,有一些快捷方式;请参阅 -C in perlrun。
UTF-8
和 utf8
有什么区别?UTF-8
是官方标准。utf8
是 Perl 为了兼容性而采用的宽松方式。如果你需要与不那么宽松的系统进行通信,你可能需要考虑使用 UTF-8
。如果你需要与过于宽松的系统进行通信,你可能需要使用 utf8
。完整的解释可以在 "UTF-8 vs. utf8 vs. UTF8" in Encode 中找到。
UTF-8
在内部被称为 utf-8-strict
。本教程始终使用 UTF-8,即使在内部实际使用 utf8 的情况下也是如此,因为区分它们可能很困难,而且大多数情况下无关紧要。
例如,utf8 可以用于 Unicode 中不存在的代码点,例如 9999999,但如果你将其编码为 UTF-8,你会得到一个替换字符(默认情况下;有关处理此问题的更多方法,请参阅 "Handling Malformed Data" in Encode)。
好吧,如果你坚持:内部格式是 utf8,而不是 UTF-8。(当它不是其他编码时。)
你搞糊涂了很好,因为你不应该依赖内部格式是任何特定的编码。但既然你问了:默认情况下,内部格式要么是 ISO-8859-1(latin-1),要么是 utf8,具体取决于字符串的历史。在 EBCDIC 平台上,这甚至可能有所不同。
Perl 知道它如何在内部存储字符串,并且会在你进行 encode
时使用该知识。换句话说:不要试图找出某个字符串的内部编码是什么,而是直接将其编码为你想要的编码。
Juerd Waalboer <#####@juerd.nl>