内容

名称

perlunifaq - Perl Unicode 常见问题解答

问答

这是一份关于 Perl 中 Unicode 的问答列表,建议在阅读 perlunitut 后阅读。

perlunitut 并不是真正的 Unicode 教程,是吗?

不,这也不是真正的 Unicode 常见问题解答。

Perl 对所有支持的字符编码都有一个抽象接口,所以这实际上是一个通用的 Encode 教程和 Encode 常见问题解答。但许多人认为 Unicode 是特殊的,有魔力的,我不想让他们失望,所以我决定把这份文档称为 Unicode 教程。

Perl 支持哪些字符编码?

要找出你的 Perl 支持哪些字符编码,请运行

perl -MEncode -le "print for Encode->encodings(':all')"

我应该使用哪个版本的 perl?

好吧,如果你可以,升级到最新版本,但至少要升级到 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)。

这种静默的隐式解码被称为“升级”。这听起来可能很积极,但最好避免它。

如果我不编码会怎样?

这取决于您输出的内容以及输出方式。

通过文件句柄输出

其他输出机制(例如,execchdir 等)

您的文本字符串将使用 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 浏览器中打开文档,并更改字符集或字符编码,直到您可以直观地确认所有字符都按预期显示。

无法可靠地自动检测编码,因此,如果人们继续向您发送没有字符集指示的数据,您可能需要教育他们。

我可以在 Perl 源代码中使用 Unicode 吗?

是的,可以!如果您的源代码是 UTF-8 编码的,您可以使用use utf8 pragma 来指示这一点。

use utf8;

这不会对您的输入或输出做任何事情。它只影响读取源代码的方式。您可以在字符串文字、标识符(但它们仍然必须是根据\w的“单词字符”)甚至自定义分隔符中使用 Unicode。

Data::Dumper 不会恢复 UTF8 标志;它坏了?

不,Data::Dumper 的 Unicode 功能应该如此。有些人抱怨它应该在使用eval再次读取数据时恢复 UTF8 标志。但是,您实际上不应该查看该标志,并且没有任何迹象表明 Data::Dumper 应该违反此规则。

以下是发生的情况:当 Perl 读取字符串文字时,它会尽可能地坚持使用 8 位编码。(但也许最初它是以 UTF-8 内部编码的,当您将其转储时。)当它必须放弃这一点,因为其他字符被添加到文本字符串中时,它会静默地将字符串升级到 UTF-8。

如果您正确地对字符串进行编码以进行输出,那么这一切都不关您的事,您可以像往常一样eval转储数据。

为什么正则表达式字符类有时只匹配 ASCII 范围内的字符?

从 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?

首先将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_utf8encode_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 标志”?

请不要考虑 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)

除了decodeencode,你也可以使用_utf8_on_utf8_off,但这种方式被认为是糟糕的风格。特别是_utf8_on 可能会很危险,原因和:utf8 一样。

对于单行代码,有一些快捷方式;请参阅 -C in perlrun

UTF-8utf8 有什么区别?

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>

另请参阅

perlunicodeperluniintroEncode