内容

名称

perluniintro - Perl Unicode 简介

描述

本文档概述了 Unicode 及如何在 Perl 中使用 Unicode。有关 Unicode 的更深入介绍,请参阅 "更多资源"

Unicode

Unicode 是一种字符集标准,旨在对世界上所有书写系统以及许多其他符号进行编码。

Unicode 和 ISO/IEC 10646 是协调一致的标准,它们统一了几乎所有其他现代字符集标准,涵盖了 80 多种书写系统和数百种语言,包括所有商业上重要的现代语言。最大的中文、日文和韩文词典中的所有字符也都进行了编码。这些标准最终将涵盖 250 多种书写系统和数千种语言中的几乎所有字符。Unicode 1.0 于 1991 年 10 月发布,6.0 于 2010 年 10 月发布。

Unicode 字符是一个抽象实体。它不绑定到任何特定的整数宽度,特别是与 C 语言的 char 无关。Unicode 是语言中立的,也是显示中立的:它不编码文本的语言,并且通常不定义字体或其他图形布局细节。Unicode 对字符和由这些字符构建的文本进行操作。

Unicode 定义了诸如 LATIN CAPITAL LETTER AGREEK SMALL LETTER ALPHA 之类的字符,以及这些字符的唯一编号,在本例中分别为 0x0041 和 0x03B1。这些唯一编号称为 码点。码点本质上是字符在所有可能的 Unicode 字符集中的位置,因此在 Perl 中,术语 序数 通常与它互换使用。

Unicode 标准更喜欢使用十六进制表示法来表示码点。如果您不熟悉 0x0041 之类的数字,请查看后面的部分,"十六进制表示法"。Unicode 标准使用 U+0041 LATIN CAPITAL LETTER A 表示法,以给出十六进制码点和字符的规范名称。

Unicode 还为字符定义了各种 属性,例如“大写”或“小写”、“十进制数字”或“标点符号”;这些属性独立于字符的名称。此外,还定义了对字符的各种操作,例如大写、小写和排序(排序)。

Unicode 逻辑“字符”实际上可以包含多个内部 实际“字符”或码点。对于西方语言,这可以通过一个 基字符(如 LATIN CAPITAL LETTER A)后跟一个或多个 修饰符(如 COMBINING ACUTE ACCENT)来充分建模。这个基字符和修饰符的序列称为 组合字符序列。一些非西方语言需要更复杂的模型,因此 Unicode 创建了 音节簇 概念,后来进一步细化为 扩展音节簇。例如,一个韩语谚文音节被认为是一个逻辑字符,但通常由三个实际的 Unicode 字符组成:一个前导辅音后跟一个内部元音,最后是一个尾随辅音。

是否将这些扩展的字形簇称为“字符”取决于你的观点。如果你是一名程序员,你可能倾向于将序列中的每个元素视为一个单元,或“字符”。然而,从用户的角度来看,整个序列可以被视为一个“字符”,因为这可能是它在用户语言环境中的显示方式。在本文件中,我们采用程序员的观点:一个“字符”就是一个 Unicode 代码点。

对于某些基本字符和修饰符的组合,存在 *预组合* 字符。例如,对于序列 LATIN CAPITAL LETTER A 后跟 COMBINING ACUTE ACCENT,存在一个单字符等效项。它被称为 LATIN CAPITAL LETTER A WITH ACUTE。然而,这些预组合字符仅适用于某些组合,主要用于支持 Unicode 与传统标准(如 ISO 8859)之间的往返转换。Unicode 使用序列,允许使用更少的构建块(代码点)来表达更多潜在的字形簇。为了支持等效形式之间的转换,还定义了各种 *规范化形式*。因此,LATIN CAPITAL LETTER A WITH ACUTE 处于 *规范化形式组合*(缩写为 NFC)中,而序列 LATIN CAPITAL LETTER A 后跟 COMBINING ACUTE ACCENT 在 *规范化形式分解*(NFD)中表示相同的字符。

由于与传统编码的向后兼容性,"每个字符一个唯一数字" 的理念略有失效:取而代之的是 "每个字符至少一个数字"。同一个字符可以在几个传统编码中以不同的方式表示。反之则不成立:某些代码点没有分配字符。首先,在已使用的块中存在未分配的代码点。其次,存在一些特殊的 Unicode 控制字符,它们不代表真正的字符。

Unicode 最初设计时,人们认为可以用 16 位字来表示世界上所有的字符;也就是说,最多需要 0x10000(或 65,536)个字符,从 0x00000xFFFF。但这很快被证明是错误的,从 Unicode 2.0(1996 年 7 月)开始,Unicode 已被定义到 21 位(0x10FFFF),而 Unicode 3.1(2001 年 3 月)定义了第一个超过 0xFFFF 的字符。前 0x10000 个字符被称为 *平面 0* 或 *基本多语言平面*(BMP)。随着 Unicode 3.1 的发布,总共定义了 17 个(是的,十七个)平面——但它们离完全充满定义的字符还很远。

当编码一种新的语言时,Unicode 通常会为其字符选择一个连续的未分配代码点的。到目前为止,这些块中的代码点数目始终是 16 的倍数。块中目前不需要的额外代码点将保留未分配,以备将来扩展。但也有过这种情况,即在后续版本中需要比可用额外代码点更多的代码点,而必须在其他地方分配一个新的块,而不是与初始块相邻,以处理溢出。因此,很早就发现“块”不是一个合适的组织原则,于是创建了Script 属性。(后来还添加了一个改进的脚本属性,即Script_Extensions 属性。)那些位于溢出块中的代码点仍然可以与原始代码点具有相同的脚本。脚本概念更符合自然语言:有Latin 脚本、Greek 脚本等等;还有几种人工脚本,例如Common,用于在多个脚本中使用的字符,例如数学符号。脚本通常跨越多个块的不同部分。有关脚本的更多信息,请参阅 "Scripts" in perlunicode。块的划分是存在的,但几乎完全是偶然的——这是字符分配方式的产物。(请注意,本段为了介绍简化了内容。Unicode 实际上并不编码语言,而是编码语言的书写系统——它们的脚本;一个脚本可以被多种语言使用。Unicode 还编码了与语言无关的内容,例如BAGGAGE CLAIM 等符号。)

Unicode 代码点只是抽象的数字。为了输入和输出这些抽象数字,必须以某种方式对这些数字进行编码序列化。Unicode 定义了几种字符编码形式,其中UTF-8 最受欢迎。UTF-8 是一种可变长度编码,它将 Unicode 字符编码为 1 到 4 个字节。其他编码包括 UTF-16 和 UTF-32 以及它们的大端和小端变体(UTF-8 与字节序无关)。ISO/IEC 10646 定义了 UCS-2 和 UCS-4 编码形式。

有关编码的更多信息——例如,要了解什么是代理字节顺序标记 (BOM)——请参阅 perlunicode

Perl 的 Unicode 支持

从 Perl v5.6.0 开始,Perl 就具备了原生处理 Unicode 的能力。然而,Perl v5.8.0 是第一个推荐用于严肃 Unicode 工作的版本。维护版本 5.6.1 修复了最初 Unicode 实现中的许多问题,但例如正则表达式在 5.6.1 中仍然无法使用 Unicode。Perl v5.14.0 是第一个 Unicode 支持(几乎)无缝集成且没有一些问题的版本。(有一些例外。首先,从 Perl 5.16.0 开始修复了 quotemeta 中的一些差异。其次,从 Perl 5.26.0 开始修复了 范围运算符 中的一些差异。第三,从 Perl 5.28.0 开始修复了 split 中的一些差异。)

为了启用这种无缝支持,您应该使用 use feature 'unicode_strings'(如果您使用 use v5.12 或更高版本,则会自动选择)。请参阅 feature。(5.14 还修复了许多错误和与 Unicode 标准的偏差。)

在 Perl v5.8.0 之前,使用 use utf8 来声明当前块或文件中的操作将是 Unicode 意识的。这种模型被发现是错误的,或者至少是笨拙的:“Unicode 性”现在与数据一起携带,而不是附加到操作。从 Perl v5.8.0 开始,只有一种情况下仍然需要显式 use utf8:如果您的 Perl 脚本本身以 UTF-8 编码,您可以在标识符名称、字符串和正则表达式字面量中使用 UTF-8,方法是说 use utf8。这不是默认设置,因为包含旧版 8 位数据的脚本会中断。请参阅 utf8

Perl 的 Unicode 模型

Perl 支持两种字符串:5.6 之前的八位字节原生字符串和 Unicode 字符串。一般原则是,Perl 尽可能地将数据保留为八位字节,但一旦不可避免地需要使用 Unicode,数据就会透明地升级为 Unicode。在 Perl v5.14.0 之前,升级并非完全透明(参见 "The "Unicode Bug"" in perlunicode),为了向后兼容,除非选择 use feature 'unicode_strings'(参见 feature)或 use v5.12(或更高版本),否则无法获得完全透明性。

在内部,Perl 目前使用平台的原生八位字符集(例如 Latin-1)或 UTF-8 来编码 Unicode 字符串,默认使用 UTF-8。具体来说,如果字符串中的所有代码点都小于或等于 0xFF,Perl 将使用原生八位字符集。否则,它将使用 UTF-8。

Perl 用户通常不需要知道或关心 Perl 如何编码其内部字符串,但这在将 Unicode 字符串输出到没有 PerlIO 层(使用“默认”编码的层)的流时变得很重要。在这种情况下,将使用内部使用的原始字节(原生字符集或 UTF-8,根据每个字符串的需要),如果这些字符串包含超过 0x00FF 的字符,则会发出“宽字符”警告。

例如,

perl -e 'print "\x{DF}\n", "\x{0100}\x{DF}\n"'

会生成原生字节和 UTF-8 的混合,并且会发出警告

Wide character in print at ...

要输出 UTF-8,请使用 :encoding:utf8 输出层。在示例程序前面添加

binmode(STDOUT, ":utf8");

可以确保输出完全是 UTF-8,并消除程序的警告。

可以使用 -C 命令行开关或 PERL_UNICODE 环境变量来启用标准文件句柄、默认 open() 层和 @ARGV 的自动 UTF-8 化,有关 -C 开关的文档,请参见 perlrun

请注意,这意味着 Perl 预计其他软件以相同的方式工作:如果 Perl 被告知 STDIN 应该是 UTF-8,但来自另一个命令的 STDIN 不是 UTF-8,Perl 可能会抱怨 UTF-8 格式错误。

所有将 Unicode 和 I/O 结合在一起的功能都需要使用新的 PerlIO 功能。几乎所有 Perl 5.8 平台都使用 PerlIO,您可以通过运行“perl -V”并查找 useperlio=define 来查看您的平台是否使用 PerlIO。

Unicode 和 EBCDIC

Perl 5.8.0 在 EBCDIC 平台上添加了对 Unicode 的支持。这种支持在后来的版本中被允许失效,但在 5.22 中被恢复。Unicode 支持的实现稍微复杂一些,因为需要额外的转换。有关更多信息,请参阅 perlebcdic

在 EBCDIC 平台上,内部 Unicode 编码形式是 UTF-EBCDIC 而不是 UTF-8。区别在于 UTF-8 是“ASCII 安全的”,因为 ASCII 字符按原样编码为 UTF-8,而 UTF-EBCDIC 是“EBCDIC 安全的”,因为所有基本字符(包括所有具有 ASCII 等效字符的字符(如 "A""0""%" 等)在 EBCDIC 和 UTF-EBCDIC 中都是相同的。通常,文档会使用“UTF-8”一词来表示 UTF-EBCDIC。本文件也是如此。

创建 Unicode

本节完全适用于从 v5.22 开始的 Perl。早期版本的各种注意事项在下面的 "早期版本注意事项" 小节中。

要在文字中创建 Unicode 字符,请在双引号字符串中使用 \N{...} 符号

my $smiley_from_name = "\N{WHITE SMILING FACE}";
my $smiley_from_code_point = "\N{U+263a}";

类似地,它们也可以在正则表达式文字中使用

$smiley =~ /\N{WHITE SMILING FACE}/;
$smiley =~ /\N{U+263a}/;

或者,从 v5.32 开始

$smiley =~ /\p{Name=WHITE SMILING FACE}/;
$smiley =~ /\p{Name=whitesmilingface}/;

在运行时,您可以使用

use charnames ();
my $hebrew_alef_from_name
                     = charnames::string_vianame("HEBREW LETTER ALEF");
my $hebrew_alef_from_code_point = charnames::string_vianame("U+05D0");

当然,ord() 会执行相反的操作:它将字符转换为代码点。

还有其他运行时选项。您可以使用 pack()

my $hebrew_alef_from_code_point = pack("U", 0x05d0);

或者您可以使用 chr(),尽管在一般情况下它不太方便

$hebrew_alef_from_code_point = chr(utf8::unicode_to_native(0x05d0));
utf8::upgrade($hebrew_alef_from_code_point);

如果参数大于 0xFF,则不需要 utf8::unicode_to_native()utf8::upgrade(),因此上面的代码可以写成

$hebrew_alef_from_code_point = chr(0x05d0);

因为 0x5d0 大于 255。

\x{}\o{} 也可以在双引号字符串中用于在编译时指定代码点,但为了与旧版本的 Perl 保持向后兼容性,对于小于 256 的代码点,将应用与 chr() 相同的规则。

utf8::unicode_to_native() 用于使 Perl 代码可移植到 EBCDIC 平台。如果您真的确定没有人会想在非 ASCII 平台上使用您的代码,则可以省略它。从 Perl v5.22 开始,在 ASCII 平台上对它的调用被优化掉了,因此添加它不会有任何性能损失。或者,您也可以简单地使用不需要它的其他结构。

有关如何查找所有这些名称和数字代码,请参阅 "更多资源"

早期版本注意事项

在 EBCDIC 平台上,在 v5.22 之前,使用 \N{U+...} 无法正常工作。

在 v5.16 之前,使用 \N{...} 带字符名称(而不是 U+... 代码点)需要一个 use charnames :full

在 v5.14 之前,\N{...} 带字符名称(而不是 U+... 代码点)存在一些错误。

charnames::string_vianame() 在 v5.14 中引入。在此之前,charnames::vianame() 应该可以工作,但前提是参数必须是 "U+..." 形式。在运行时通过字符名称处理 Unicode 的最佳方法可能是

use charnames ();
my $hebrew_alef_from_name
                 = pack("U", charnames::vianame("HEBREW LETTER ALEF"));

处理 Unicode

在大多数情况下,处理 Unicode 是透明的:只需像往常一样使用字符串即可。index()length()substr() 等函数将在 Unicode 字符上工作;正则表达式将在 Unicode 字符上工作(参见 perlunicodeperlretut)。

请注意,Perl 将音节群视为单独的字符,因此例如

print length("\N{LATIN CAPITAL LETTER A}\N{COMBINING ACUTE ACCENT}"),
      "\n";

将打印 2,而不是 1。唯一的例外是正则表达式有 \X 用于匹配扩展音节群。(因此,正则表达式中的 \X 将匹配这两个示例字符的整个序列。)

然而,在处理遗留编码、I/O 和某些特殊情况时,情况并非如此透明

遗留编码

当您将遗留数据和 Unicode 结合在一起时,需要将遗留数据升级到 Unicode。通常,遗留数据被假定为 ISO 8859-1(或 EBCDIC,如果适用)。

Encode 模块了解许多编码,并提供用于在这些编码之间进行转换的接口

use Encode 'decode';
$data = decode("iso-8859-3", $data); # convert from legacy

Unicode I/O

通常,写入 Unicode 数据

print FH $some_string_with_unicode, "\n";

会生成 Perl 用于内部编码 Unicode 字符串的原始字节。Perl 的内部编码取决于系统以及字符串中当时存在的字符。如果任何字符的代码点为 0x100 或更高,您将收到警告。为了确保输出以您想要的编码显式呈现,并避免警告,请使用所需的编码打开流。以下是一些示例

open FH, ">:utf8", "file";

open FH, ">:encoding(ucs2)",      "file";
open FH, ">:encoding(UTF-8)",     "file";
open FH, ">:encoding(shift_jis)", "file";

以及在已打开的流上,使用 binmode()

binmode(STDOUT, ":utf8");

binmode(STDOUT, ":encoding(ucs2)");
binmode(STDOUT, ":encoding(UTF-8)");
binmode(STDOUT, ":encoding(shift_jis)");

编码名称的匹配比较宽松:不区分大小写,并且许多编码都有多个别名。请注意,:utf8 层必须始终按原样指定;它受编码名称宽松匹配的影响。另请注意,目前 :utf8 对输入是不安全的,因为它接受数据而不验证其是否确实是有效的 UTF-8;您应该改为使用 :encoding(UTF-8)(带或不带连字符)。

有关 :utf8 层,请参阅 PerlIO;有关 :encoding() 层,请参阅 PerlIO::encodingEncode::PerlIO;有关 Encode 模块支持的许多编码,请参阅 Encode::Supported

读取您知道以某种 Unicode 或传统编码编码的文件不会神奇地将数据在 Perl 的眼中转换为 Unicode。为此,请在打开文件时指定适当的层。

open(my $fh,'<:encoding(UTF-8)', 'anything');
my $line_of_unicode = <$fh>;

open(my $fh,'<:encoding(Big5)', 'anything');
my $line_of_unicode = <$fh>;

I/O 层也可以使用 open 编译指示更灵活地指定。请参阅 open,或查看以下示例。

use open ':encoding(UTF-8)'; # input/output default encoding will be
                             # UTF-8
open X, ">file";
print X chr(0x100), "\n";
close X;
open Y, "<file";
printf "%#x\n", ord(<Y>); # this should print 0x100
close Y;

使用 open 编译指示,您可以使用 :locale 层。

BEGIN { $ENV{LC_ALL} = $ENV{LANG} = 'ru_RU.KOI8-R' }
# the :locale will probe the locale environment variables like
# LC_ALL
use open OUT => ':locale'; # russki parusski
open(O, ">koi8");
print O chr(0x430); # Unicode CYRILLIC SMALL LETTER A = KOI8-R 0xc1
close O;
open(I, "<koi8");
printf "%#x\n", ord(<I>), "\n"; # this should print 0xc1
close I;

这些方法在 I/O 流上安装一个透明过滤器,该过滤器在从流中读取数据时将其从指定编码转换为 Unicode。结果始终是 Unicode。

open 编译指示通过设置默认层来影响编译指示之后的所有 open() 调用。如果您只想影响某些流,请在 open() 调用中直接使用显式层。

您可以使用 binmode() 在已打开的流上切换编码;请参阅 "binmode" in perlfunc

:locale 目前不适用于 open()binmode(),仅适用于 open 编译指示。:utf8:encoding(...) 方法适用于 open()binmode()open 编译指示。

类似地,您可以在输出流上使用这些 I/O 层,以便在将 Unicode 写入流时自动将其转换为指定的编码。例如,以下代码段将文件 "text.jis"(以 ISO-2022-JP 编码,也称为 JIS)的内容复制到文件 "text.utf8",以 UTF-8 编码。

open(my $nihongo, '<:encoding(iso-2022-jp)', 'text.jis');
open(my $unicode, '>:utf8',                  'text.utf8');
while (<$nihongo>) { print $unicode $_ }

编码的命名,无论是通过 open() 还是通过 open 编译指示,都允许使用灵活的名称:koi8-rKOI8R 都将被理解。

ISO、MIME、IANA 和各种其他标准化组织认可的常用编码都得到认可;有关更详细的列表,请参阅 Encode::Supported

read() 读取字符并返回字符数。seek()tell() 操作字节计数,sysseek() 也是如此。

sysread()syswrite() 不应在具有字符编码层的句柄上使用,它们的行为很糟糕,这种行为从 perl 5.24 开始就被弃用。

请注意,由于默认行为是在输入时不进行任何转换(如果不存在默认层),因此很容易错误地编写代码,该代码通过重复编码数据来不断扩展文件。

# BAD CODE WARNING
open F, "file";
local $/; ## read in the whole file of 8-bit characters
$t = <F>;
close F;
open F, ">:encoding(UTF-8)", "file";
print F $t; ## convert to UTF-8 on output
close F;

如果您运行此代码两次,文件的内容将被两次 UTF-8 编码。使用 use open ':encoding(UTF-8)' 可以避免此错误,或者显式地将文件也以 UTF-8 格式打开以进行输入。

注意:utf8:encoding 功能仅在您的 Perl 使用 PerlIO 构建时才有效,这是大多数系统上的默认设置。

将 Unicode 显示为文本

有时您可能希望将包含 Unicode 的 Perl 标量显示为简单的 ASCII(或 EBCDIC)文本。以下子例程将转换其参数,以便代码点大于 255 的 Unicode 字符显示为 \x{...},控制字符(如 \n)显示为 \x..,其余字符则显示为自身

sub nice_string {
       join("",
       map { $_ > 255                    # if wide character...
             ? sprintf("\\x{%04X}", $_)  # \x{...}
             : chr($_) =~ /[[:cntrl:]]/  # else if control character...
               ? sprintf("\\x%02X", $_)  # \x..
               : quotemeta(chr($_))      # else quoted or as themselves
       } unpack("W*", $_[0]));           # unpack Unicode characters
  }

例如,

nice_string("foo\x{100}bar\n")

返回字符串

'foo\x{0100}bar\x0A'

该字符串已准备好打印。

(此处使用 \\x{} 而不是 \\N{},因为您最有可能希望看到本机值。)

特殊情况

高级主题

其他

问答

十六进制表示法

Unicode 标准更喜欢使用十六进制表示法,因为这更清楚地显示了 Unicode 如何划分为 256 个字符的块。十六进制也比十进制更短。你也可以使用十进制表示法,但学习使用十六进制会让使用 Unicode 标准变得更容易。例如,U+HHHH 表示法使用十六进制。

0x 前缀表示十六进制数,数字是 0-9 以及 a-f(或 A-F,大小写无关)。每个十六进制数字代表四个位,或半个字节。print 0x..., "\n" 将以十进制显示十六进制数,而 printf "%x\n", $decimal 将以十六进制显示十进制数。如果你只有十六进制数的“十六进制数字”,你可以使用 hex() 函数。

print 0x0009, "\n";    # 9
print 0x000a, "\n";    # 10
print 0x000f, "\n";    # 15
print 0x0010, "\n";    # 16
print 0x0011, "\n";    # 17
print 0x0100, "\n";    # 256

print 0x0041, "\n";    # 65

printf "%x\n",  65;    # 41
printf "%#x\n", 65;    # 0x41

print hex("41"), "\n"; # 65

更多资源

旧版 Perl 中的 UNICODE

如果您无法将 Perl 升级到 5.8.0 或更高版本,您仍然可以通过使用来自 CPAN 的 Unicode::StringUnicode::Map8Unicode::Map 模块来进行一些 Unicode 处理。如果您安装了 GNU recode,您还可以使用 Perl 前端 Convert::Recode 进行字符转换。

以下是将 ISO 8859-1 (Latin-1) 字节快速转换为 UTF-8 字节以及反向转换的代码,即使在旧版 Perl 5 版本中也能正常工作。

# ISO 8859-1 to UTF-8
s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg;

# UTF-8 to ISO 8859-1
s/([\xC2\xC3])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg;

另请参阅

perlunitutperlunicodeEncodeopenutf8bytesperlretutperlrunUnicode::CollateUnicode::NormalizeUnicode::UCD

致谢

感谢 [email protected][email protected][email protected][email protected] 邮件列表的热心读者提供宝贵的反馈。

作者、版权和许可

版权所有 2001-2011 Jarkko Hietaniemi <[email protected]>。现在由 Perl 5 维护者维护。

本文件可以在与 Perl 本身相同的条款下分发。