Perl ord 和 chr 如何处理 Unicode。

7
令我感到惊恐的是,我刚刚发现chr在Unicode上不起作用,尽管它确实做了一些事情。这个人页却一点都不清楚。

返回在字符集中由该数字表示的字符。例如,chr(65)代表ASCII或Unicode中的"A",而chr(0x263a)则是一个Unicode笑脸。

事实上,我可以使用以下方法打印一个笑脸:
perl -e 'print chr(0x263a)'

但是像 chr(0x00C0) 这样的东西不起作用。我发现我的 perl v5.10.1 有点古老,但当我在源代码中粘贴各种奇怪的字母时,一切都正常。

我尝试了一些有趣的事情,比如 use utf8use encoding 'utf8',但我没有尝试 use v5.12use feature 'unicode_strings',因为它们与我的版本不兼容,我正在使用 Encode::decode 进行测试,以找出我不需要解码,因为我没有要解码的字节数组。我阅读了比以往更多的文档,并发现了一些有趣的事情,但没有什么有用的。看起来像是一种 Unicode Bug ,但给出的解决方案无法使用。此外,我不关心整个字符串语义,我只需要一个简单的函数。

那么,我如何将一个数字转换为由相应单个字符组成的字符串,例如 real_chr(0xC0) eq 'À'


我得到的第一个答案几乎解释了所有关于 IO 的内容,但我仍然不理解为什么。

#!/usr/bin/perl -w
use strict;
use utf8;
use encoding 'utf8';

print chr(0x00C0) eq 'À' ? 'eq1' : 'ne1', " - ", chr(0x263a) eq '☺' ? 'eq1' : 'ne1', "\n";

print 'À' =~ /\w/ ? "match1" : "no_match1", " - ", chr(0x00C0) =~ /\w/ ? "match2" : "no_match2", "\n";

打印

ne1 - eq1
match1 - no_match2

这意味着手动输入的 'À'chr(0x00C0) 不同。此外,前者是一个单词组成字符(正确!),而后者不是(但应该是!)。

@D.Shawley:Linux 2.6.32-42-generic,x86_64 GNU/Linux,Ubuntu 10.4,因此utf8是本地的。 - maaartinus
Á 的 UTF8 八位序列为 C3 81C1 是 ISO-8859-1 代码点。我的 Perl 程序能力有些薄弱,否则我会提出一个答案。 - D.Shawley
一些文档在这方面比较薄弱,但是即使在早期的 Perl 5.10.1 版本中,UTF-8 的实现也非常强大。在使用 Perl 处理 Unicode 之前,我建议先阅读 perlunitutperluniintro。在你的情况下,chr 不是问题,问题在于你没有为 UTF-8 进行编码和解码。如果你要输出 UTF-8(或任何其他编码),你的字符字符串需要先转换为八位字节。 - zostay
@ikegami:删除它会将输出更改为eq1 - eq1; match1 - no_match2。因此,我有两个相等的字符串,只有其中一个匹配。 - maaartinus
Unicode正则表达式在Perl 5.14之前的版本中存在问题。在Perl 5.14中,您的第二个正则表达式可以在不使用use encoding并附加/u修饰符的情况下进行修复。请参阅perlre中的字符集修饰符 - zostay
1个回答

11
首先,
perl -le'print chr(0x263A);'

有缺陷。Perl甚至告诉你这一点:
Wide character in print at -e line 1.

这并不算是“工作”。虽然它们在未能提供你想要的方面存在差异,但以下两种情况都不能给你想要的东西:
perl -le'print chr(0x263A);'

perl -le'print chr(0x00C0);'

要正确输出这些Unicode代码点的UTF-8编码,您需要告诉Perl使用UTF-8对Unicode代码点进行编码。
$ perl -le'use open ":std", ":encoding(UTF-8)"; print chr(0x263A);'
☺

$ perl -le'use open ":std", ":encoding(UTF-8)"; print chr(0x00C0);'
À

的英文原文和上文无关,应该是一个分割线标签。下面是翻译:

现在我们来谈一下“为什么”。

文件句柄只能传输字节,因此除非你告诉它,否则Perl文件句柄会预期字节。这意味着你提供给print的字符串不能包含超过255个字符的内容,或者换句话说,不能包含超出字节的字符。输出正好是你提供的内容:

$ perl -e'print map chr, 0x00, 0x65, 0xC0, 0xF0' | od -t x1
0000000 00 65 c0 f0
0000004

这是有用的。这与您想要的不同,但这并不意味着它是错误的。如果您想要不同的东西,您只需要告诉 Perl 您想要什么。
通过添加一个:编码层,句柄现在期望一个 Unicode 字符串,或者我称之为“文本”。该层告诉 Perl 如何将文本转换为字节。
$ perl -e'
   use open ":std", ":encoding(UTF-8)";
   print map chr, 0x00, 0x65, 0xC0, 0xF0, 0x263a;
' | od -t x1
0000000 00 65 c3 80 c3 b0 e2 98 ba
0000011

你说得对,chr 对 Unicode 没有了解或关注。像lengthsubstrordreverse一样,chr 实现了基本的字符串函数,而不是 Unicode 函数。但这并不意味着它不能用来处理文本字符串。就像你看到的,问题不在于 chr,而在于你在构建字符串后对其所做的操作。

一个字符是字符串的元素,而一个字符是一个数字。这意味着一个字符串只是一串数字序列。无论你是将这些数字视为 Unicode 码点(文本)、打包的 IP 地址还是温度测量值,完全取决于你和传递给字符串的函数。

以下是一些在接收操作数时赋予字符串意义的运算符示例:

  • m// 期望一个 Unicode 码点字符串。
  • connect 期望表示sockaddr_in 结构的字节序列。
  • 没有:encoding 的句柄的print 期望一个字节序列。
  • 带有:encoding的句柄的print 期望一个 Unicode 码点序列。
  • 等等

那么我该怎样将一个数字转换为只包含对应单个字符的字符串,以便例如 real_chr(0xC0) eq 'À' 成立?

chr(0xC0) eq 'À' 是成立的。你是否记得使用 use utf8; 告诉 Perl 你使用 UTF-8 编码了源代码?如果没有告诉 Perl,实际上 Perl 在 RHS 上看到的是一个两个字符的字符串。


关于你添加的问题:

encoding pragma 存在问题。我不建议使用它。相反,请使用

use open ':std', ':encoding(UTF-8)';

“那会解决其中一个问题。你遇到的另一个问题是…”
chr(0x00C0) =~ /\w/

这是一个已知的错误,出于向后兼容性的原因故意保留为不正常状态。也就是说,除非您按以下方式请求更高版本的语言:
use 5.014;    # use 5.012; *might* suffice.

一个可行的解决方法,可以适用于版本5.8及更早的版本:
my $x = chr(0x00C0);
utf8::upgrade($x);
$x =~ /\w/

如果您想要更手动的解决方案,您也可以使用Encode qw( encode ); print encode(chr(0x00c0)),这几乎与上面的“:encoding(UTF-8)”相同,但没有I/O句柄。 - zostay
@zostay’╝īÕ║öĶ»źµś»encode('UTF-8', chr(0x00c0))µł¢ĶĆģencode_utf8(chr(0x00c0))’╝īµ▓ĪķöÖŃĆé - ikegami
@ikegami:你对IO肯定是正确的,但我不关心IO,因为那只是一个调试输出。我不会输出除ASCII之外的任何内容,这就是为什么我忽略了警告。我仍然不明白发生了什么,请看我的编辑。 - maaartinus
@maaartinus,已回答新增的问题。请注意,5.14是Perl的最旧支持版本。 - ikegami
@ikegami:就是这样,我的版本不能使用use 5.014,但是utf8::upgrade很有帮助! - maaartinus

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