提问人:w.k 提问时间:5/28/2011 最后编辑:Jensw.k 更新时间:11/16/2023 访问量:112552
为什么现代 Perl 默认避免使用 UTF-8?
Why does modern Perl avoid UTF-8 by default?
问:
我想知道为什么大多数使用 Perl 构建的现代解决方案默认不启用 UTF-8。
我知道核心Perl脚本存在许多遗留问题,它可能会破坏一些东西。但是,从我的角度来看,在21世纪,大型新项目(或具有大视野的项目)应该使他们的软件从头开始证明UTF-8。我仍然没有看到它发生。例如,Moose 启用 strict 和 warnings,但不支持 Unicode。Modern::P erl 也减少了样板,但没有 UTF-8 处理。
为什么?在2011年的现代Perl项目中,是否有一些理由避免使用UTF-8?
评论@tchrist太长了,所以我在这里添加它。
看来我没有说清楚。让我试着补充一些东西。
tchrist 和我对情况的看法非常相似,但我们的结论完全相反。我同意,Unicode的情况很复杂,但这就是为什么我们(Perl用户和编码人员)需要一些层(或编译指示),使UTF-8处理像现在一样容易。
tchrist 指出了要涵盖的许多方面,我将阅读并思考它们数天甚至数周。不过,这不是我的重点。tchrist 试图证明没有一种“启用 UTF-8”的方法。我没有那么多知识可以反驳这一点。所以,我坚持使用活生生的例子。
我玩了 Rakudo,UTF-8 就在我需要的时候。我没有任何问题,它只是起作用了。也许在更深层次的地方有一些限制,但在开始时,我测试的所有内容都按预期工作。
这难道不应该也是现代 Perl 5 的目标吗?我更强调的是:我不建议将 UTF-8 作为核心 Perl 的默认字符集,我建议那些开发新项目的人可以快速触发它。
另一个例子,但语气更消极。框架应该使开发更容易。几年前,我尝试过 Web 框架,但只是把它们扔掉了,因为“启用 UTF-8”太晦涩难懂了。我没有找到如何以及在哪里挂钩Unicode支持。这太耗时了,我发现走老路更容易。现在我看到这里有一个赏金来处理 Mason 2 的相同问题:如何使 Mason2 UTF-8 干净?因此,这是一个相当新的框架,但将其与 UTF-8 一起使用需要对其内部结构有深入的了解。它就像一个大红色的牌子:停下来,不要用我!
我真的很喜欢Perl。但是处理Unicode是痛苦的。我仍然发现自己撞墙。在某种程度上,tchrist 是正确的,它回答了我的问题:新项目不会吸引 UTF-8,因为它在 Perl 5 中太复杂了。
答:
在野外有大量非常可怕的古代代码,其中大部分是以普通CPAN模块的形式出现的。我发现,如果我使用可能受 Unicode 影响的外部模块,我必须相当小心地启用 Unicode,并且仍在尝试识别和修复我经常使用的几个 Perl 脚本中的一些与 Unicode 相关的故障(特别是,iTiVo 由于转码问题,iTiVo 在任何非 7 位 ASCII 上都失败了)。
评论
-C
$LANG
$LC_ALL
-C
PERL5OPT
-C
print while <>
-C
-CSDA
最简单的 C:7 个离散建议
将变量设置为 。这使得所有 Perl 脚本都解码为 UTF-8 字符串,并将 stdin、stdout 和 stderr 这三个的编码设置为 UTF-8。这两者都是全局效应,而不是词汇效应。
PERL_UNICODE
AS
@ARGV
在源文件(程序、模块、库、hickey)的顶部,通过以下方式突出显示您正在运行 perl 5.12 或更高版本:
do
use v5.12; # minimal for unicode string feature use v5.14; # optimal for unicode string feature
启用警告,因为前面的声明仅启用限制和功能,而不启用警告。我还建议将 Unicode 警告提升为例外,因此请同时使用这两行,而不仅仅是其中一行。但请注意,在 v5.14 中,warning 类包含其他三个子警告,它们都可以单独启用:、 和 。您可能希望对这些施加更大的控制。
utf8
nonchar
surrogate
non_unicode
use warnings; use warnings qw( FATAL utf8 );
声明此源单元编码为 UTF-8。虽然曾几何时,这个实用主义做了其他事情,但现在它只服务于一个单一的目的,没有其他目的:
use utf8;
声明在此词法范围内打开文件句柄但未在其他地方打开的任何内容都假定该流是以 UTF-8 编码的,除非您另有说明。这样您就不会影响其他模块或其他程序的代码。
use open qw( :encoding(UTF-8) :std );
通过 启用命名字符。
\N{CHARNAME}
use charnames qw( :full :short );
如果有句柄,则必须显式设置其编码。如果你希望它是 UTF-8,那么说:
DATA
binmode(DATA, ":encoding(UTF-8)");
当然,你最终可能会发现自己关心的其他问题无穷无尽,但这些足以接近“让一切都与 UTF-8 一起工作”的状态目标,尽管这些术语的意义有所减弱。
另一个编译指示,虽然它与Unicode无关,但:
use autodie;
强烈建议这样做。
🌴 🐪🐫🐪 🌞 你去照样🌞 🐪🐫🐪 🐁做
🎁 🐪 Boiler⸗plate for Unicode⸗Aware Code 🐪 🎁
这些天我自己的样板文件往往是这样的:
use 5.014;
use utf8;
use strict;
use autodie;
use warnings;
use warnings qw< FATAL utf8 >;
use open qw< :std :utf8 >;
use charnames qw< :full >;
use feature qw< unicode_strings >;
use File::Basename qw< basename >;
use Carp qw< carp croak confess cluck >;
use Encode qw< encode decode >;
use Unicode::Normalize qw< NFD NFC >;
END { close STDOUT }
if (grep /\P{ASCII}/ => @ARGV) {
@ARGV = map { decode("UTF-8", $_) } @ARGV;
}
$0 = basename($0); # shorter messages
$| = 1;
binmode(DATA, ":utf8");
# give a full stack dump on any untrapped exceptions
local $SIG{__DIE__} = sub {
confess "Uncaught exception: @_" unless $^S;
};
# now promote run-time warnings into stack-dumped
# exceptions *unless* we're in an try block, in
# which case just cluck the stack dump instead
local $SIG{__WARN__} = sub {
if ($^S) { cluck "Trapped warning: @_" }
else { confess "Deadly warning: @_" }
};
while (<>) {
chomp;
$_ = NFD($_);
...
} continue {
say NFC($_);
}
__END__
🎅 N o M a g i c B u l l e t 🎅
说“Perl应该[以某种方式!默认启用 Unicode“甚至没有开始考虑在某种罕见和孤立的情况下说出足够多的话,甚至有点用处。Unicode 不仅仅是一个更大的字符库;这也是这些角色以多种方式互动的方式。
即使是(一些)人似乎认为他们想要的头脑简单的最低限度措施,也一定会悲惨地破坏数百万行代码,这些代码没有机会“升级”到你华丽的新美丽新世界现代性。
这比人们假装的要复杂得多。在过去的几年里,我一直在思考这个问题。我很想被证明我错了。但我不认为我是。从根本上说,Unicode 比你想强加给它的模型更复杂,而且这里的复杂性是你永远无法掩盖的。如果你尝试,你要么会破坏你自己的代码,要么破坏别人的代码。在某些时候,您只需要分解并了解Unicode是关于什么的。你不能假装它不是。
🐪 不遗余力地使Unicode变得简单,远远超过我用过的任何其他东西。如果您认为这很糟糕,请尝试一段时间。然后回到🐪:要么你回到一个更美好的世界,要么你将带来同样的知识,以便我们可以利用你的新知识在这些事情上做得更好🐪。
💡 Unicode 的想法 ⸗ Aware 🐪 洗衣清单 💡
至少,正如您所说,以下是“默认启用 Unicode”所需的🐪一些东西:
默认情况下,所有🐪源代码都应采用 UTF-8 格式。你可以用 或 来获得它。
use utf8
export PERL5OPTS=-Mutf8
🐪 句柄应为 UTF-8。您必须按包执行此操作,如 。
DATA
binmode(DATA, ":encoding(UTF-8)")
默认情况下,脚本的🐪程序参数应理解为 UTF-8。、 或 或 。
export PERL_UNICODE=A
perl -CA
export PERL5OPTS=-CA
标准输入、输出和错误流应默认为 UTF-8。 对于所有这些,或 、 和/或仅针对其中的一些。这就像.
export PERL_UNICODE=S
I
O
E
perl -CS
除非另有声明,否则打开的🐪任何其他句柄都应被视为 UTF-8; 或与这些特定的一起和为这些特定的; 会起作用。这适用于所有这些。
export PERL_UNICODE=D
i
o
export PERL5OPTS=-CD
-CSAD
覆盖两个基地以及您打开的所有流。请参见 uniquote。
export PERL5OPTS=-Mopen=:utf8,:std
您不想错过 UTF-8 编码错误。尝试。并确保你的输入流始终是 d 到 ,而不仅仅是 。
export PERL5OPTS=-Mwarnings=FATAL,utf8
binmode
:encoding(UTF-8)
:utf8
介于 128-255 之间的代码点应理解🐪为相应的 Unicode 代码点,而不仅仅是不适当的二进制值。 或。这将使 和 .一个简单或更好的人也会得到它。
use feature "unicode_strings"
export PERL5OPTS=-Mfeature=unicode_strings
uc("\xDF") eq "SS"
"\xE9" =~ /\w/
export PERL5OPTS=-Mv5.12
默认情况下,命名的 Unicode 字符未启用,因此请添加或类似字符。请参阅 uninames 和 tcgrep。
export PERL5OPTS=-Mcharnames=:full,:short,latin,greek
您几乎总是需要访问标准
Unicode::Normalize
模块中的各种分解类型的函数。,然后始终通过 NFD 运行传入内容,并从 NFC 运行出站内容。据我所知,目前还没有 I/O 层,但请参阅 nfc、nfd、nfkd 和 nfkc。export PERL5OPTS=-MUnicode::Normalize=NFD,NFKD,NFC,NFKD
使用 、 、 、 、 、 &c&cc 的🐪字符串比较总是错误的。因此,您需要 .不妨将其添加到您的 .您可以缓存密钥以进行二进制比较。
eq
ne
lc
cmp
sort
@a = sort @b
@a = Unicode::Collate->new->sort(@b)
export PERL5OPTS=-MUnicode::Collate
🐪 内置的 Unicode 数据喜欢并做错事。前者需要使用
Unicode::GCString
模块,后者需要同时使用Unicode::LineBreak
模块。请参阅 uwc 和 unifmt。printf
write
如果你想让它们算作整数,那么你将不得不通过
Unicode::UCD::num
函数运行你的捕获,因为 🐪 的内置 atoi(3) 目前还不够聪明。\d+
您将在文件系统上👽遇到文件系统问题。一些文件系统以静默方式强制执行到 NFC 的转换;其他人则默默地强制转换为 NFD。其他人仍然在做其他事情。有些人甚至完全忽略了这个问题,这导致了更大的问题。因此,您必须自己进行 NFC/NFD 处理以保持理智。
🐪 所有涉及 或 和 的代码都必须更改,包括 、 和 。它应该是一个尖叫的危险信号,表明你的代码被破坏了。但目前尚不清楚它必须如何改变。获得正确的属性,并了解它们的折叠,比你想象的要难。我每天都使用 unichars 和 uniprops。
a-z
A-Z
m//
s///
tr///
使用 的代码几乎和使用 .您需要改用,并知道原因。是的,并且与 和 不同。
\p{Lu}
[A-Za-z]
\p{Upper}
\p{Lowercase}
\p{Lower}
\p{Ll}
\p{Lowercase_Letter}
使用的代码甚至更糟。它不能使用 or ;它需要使用 .并非所有字母都是字母,你知道的!
[a-zA-Z]
\pL
\p{Letter}
\p{Alphabetic}
如果你正在寻找🐪带有 的变量,那么你就有问题了。您需要查找 ,即使这样也不考虑标点符号变量或包变量。
/[\$\@\%]\w+/
/[\$\@\%]\p{IDS}\p{IDC}*/
如果要检查空格,则应在 和 之间进行选择,具体取决于。而且你永远不应该使用 ,因为它并不意味着 ,与流行的看法相反。
\h
\v
\s
[\h\v]
如果你使用的是线边界,甚至 ,那么你就做错了。你必须用 ,这是不一样的!
\n
\r\n
\R
如果您不知道何时以及是否调用 Unicode::Stringprep,那么您最好学习。
不区分大小写的比较需要检查两个事物是否是相同的字母,无论它们的变音符号等如何。最简单的方法是使用标准的 Unicode::Collate 模块。.还有方法等,您可能也应该了解 和 方法。与内置相比,🐪这些具有明显的优势。
Unicode::Collate->new(level => 1)->cmp($a, $b)
eq
match
substr
有时这还不够,您需要 Unicode::Collate::Locale 模块,如 in 所示。认为这是真的,但是错误的。同样,“ae”和“æ”是如果您不使用语言环境,或者如果您使用英语语言环境,但它们在冰岛语言环境中是不同的。现在怎么办?我告诉你,这很难。你可以用ucsort来测试其中的一些东西。
Unicode::Collate::Locale->new(locale => "de__phonebook", level => 1)->cmp($a, $b)
Unicode::Collate::->new(level => 1)->eq("d", "ð")
Unicode::Collate::Locale->new(locale=>"is",level => 1)->eq("d", " ð")
eq
考虑如何匹配字符串“niño”中的模式 CVCV(辅音、元音、辅音、元音)。它的NFD形式——你最好记得把它放进去——变成了“nin\x{303}o”。现在你要做什么?即使假装元音是(顺便说一句,这是错误的),你也无法做类似的事情,因为即使在 NFD 中,像“ø”这样的码位也不会分解!但是,它将使用我刚才向您展示的 UCA 比较来测试等于“o”。你不能依赖NFD,你必须依靠UCA。
[aeiou]
(?=[aeiou])\X)
💩 A s s u m e B r o k e n n e s s 💩
这还不是全部。人们对Unicode有一百万个破碎的假设。在他们理解这些事情之前,他们的🐪代码将被破解。
假定它可以在不指定编码的情况下打开文本文件的代码已损坏。
假定默认编码是某种本机平台编码的代码已损坏。
假设日语或中文网页在 UTF-16 中占用的空间比在 UTF-8 中占用的空间少的代码是错误的。
假设 Perl 在内部使用 UTF-8 的代码是错误的。
假定编码错误总是会引发异常的代码是错误的。
假设 Perl 代码点仅限于 0x10_FFFF 的代码是错误的。
假设您可以设置为适用于任何有效行分隔符的代码是错误的。
$/
假设在大小写折叠上往返相等的代码(如 或)是完全错误的。考虑 和 都是 ,但不可能同时返回这两个。
lc(uc($s)) eq $s
uc(lc($s)) eq $s
uc("σ")
uc("ς")
"Σ"
lc("Σ")
假设每个小写代码点都有一个不同的大写代码点,反之亦然,代码被破坏。例如,是没有大写字母的小写字母;而 和 都是字母,但它们不是小写字母;但是,它们都是小写代码点,没有相应的大写版本。明白了吗?它们不是 ,尽管它们既是 和 。
"ª"
"ᵃ"
"ᴬ"
\p{Lowercase_Letter}
\p{Letter}
\p{Lowercase}
假定更改大小写不会更改字符串长度的代码已损坏。
假设只有两种情况的代码被破坏。还有标题大小写。
假设只有字母大小写的代码已损坏。除了字母之外,事实证明,数字、符号甚至标记都有大小写。事实上,改变大小写甚至可以使某物改变其主要的一般类别,比如变成 .它还可以使它从一个脚本切换到另一个脚本。
\p{Mark}
\p{Letter}
假定大小写从不依赖于区域设置的代码已损坏。
假设 Unicode 给出有关 POSIX 语言环境的无花果的代码已损坏。
假设您可以删除变音符号以获得基本 ASCII 字母的代码是邪恶的、破碎的、脑损伤的、错误的,并且是死刑的理由。
假设变音符号和标记是一回事的代码被破坏了。
\p{Diacritic}
\p{Mark}
假设的代码涵盖的代码与损坏的代码一样多。
\p{GC=Dash_Punctuation}
\p{Dash}
假设破折号、连字符和减号彼此相同,或者每个代码只有一个,则代码是错误的。
假定每个代码点占用不超过一个打印列的代码已损坏。
假定所有字符占用零打印列的代码已损坏。
\p{Mark}
假定看起来相似的字符相似的代码已损坏。
假定看起来不相似的字符不相似的代码已损坏。
假设一行中只有一个代码点可以匹配的代码点数量有限制是错误的。
\X
假设永远不能以字符开头的代码是错误的。
\X
\p{Mark}
假设永远不能包含两个非字符的代码是错误的。
\X
\p{Mark}
假设它不能使用的代码是错误的。
"\x{FFFF}"
假设需要两个 UTF-16(代理)代码单元的非 BMP 代码点将编码为两个单独的 UTF-8 字符(每个代码单元一个)的代码是错误的。它没有:它编码到单个代码点。
如果将 BOM 放在生成的 UTF-8 的开头,则将带有前导 BOM 的 UTF-16 或 UTF-32 转码为 UTF-8 的代码将被破坏。这太愚蠢了,工程师应该摘掉他们的眼睑。
假定 CESU-8 是有效 UTF 编码的代码是错误的。同样,将 U+0000 编码为 UTF-8 的代码也是错误的。这些家伙也应该得到眼睑治疗。
"\xC0\x80"
假设字符(如总是指向右边,总是指向左边)的代码是错误的,因为它们实际上并非如此。
>
<
假设如果您首先输出字符,然后输出字符,则这些代码将显示为错误。有时他们没有。
X
Y
XY
假设 ASCII 足以正确编写英语的代码是愚蠢的、短视的、文盲的、破碎的、邪恶的和错误的。砍掉他们的头!如果这看起来太极端了,我们可以妥协:从今以后,他们只能用一只脚的大脚趾打字。(其余的将被胶带粘住。
假定所有代码点都是可见字符的代码是错误的。
\p{Math}
假定仅包含字母、数字和下划线的代码是错误的。
\w
假定 和 是标点符号的代码是错误的。
^
~
假定具有变音符号的代码是错误的。
ü
认为包含任何字母之类的代码是错误的。
₨
相信的代码与被严重破坏的代码相同。
\p{InLatin}
\p{Latin}
认为这几乎永远有用的代码几乎肯定是错误的。
\p{InLatin}
认为作为某个字母表中的第一个字母和同一字母表中的最后一个字母给出的代码,具有任何意义,几乎总是完全破碎的、错误的和毫无意义的。
$FIRST_LETTER
$LAST_LETTER
[${FIRST_LETTER}-${LAST_LETTER}]
认为某人的名字只能包含某些字符的代码是愚蠢的、冒犯性的和错误的。
试图将Unicode简化为ASCII的代码不仅是错误的,其肇事者永远不应该被允许再次从事编程工作。时期。我什至不肯定他们甚至应该被允许再次看到,因为到目前为止,这显然对他们没有多大好处。
认为有某种方法可以假装文本文件编码不存在的代码是损坏且危险的。还不如把另一只眼睛也捅出来。
将未知字符转换为的代码是坏的、愚蠢的、脑死亡的,并且与标准建议背道而驰,即不要这样做!为什么不RTFM。
?
认为自己可以可靠地猜测未标记文本文件的编码的代码犯了傲慢和幼稚的致命混杂,只有宙斯的闪电才能解决。
认为您可以使用🐪宽度来填充和对齐 Unicode 数据的代码是错误的。
printf
代码认为,一旦你成功地创建了一个给定名称的文件,当你运行或在其封闭的目录上时,你实际上会发现该文件的名称与你创建它时有问题、损坏和错误。不要再对此感到惊讶了!
ls
readdir
认为 UTF-16 是固定宽度编码的代码是愚蠢的、错误的和错误的。吊销他们的编程许可证。
将一个平面上的代码点与任何其他平面上的代码点区别一丁点的代码视为事实上的破坏和错误。回到学校。
认为这样的东西只能匹配或损坏和错误的代码。你会感到惊讶。
/s/i
"S"
"s"
用于查找字素簇而不是 use 的代码已损坏且错误。
\PM\pM*
\X
应该全心全意地鼓励那些想要回到 ASCII 世界的人这样做,为了纪念他们的光荣升级,他们应该免费获得一台预电动手动打字机,以满足他们所有的数据输入需求。发送给他们的消息应通过 ᴀʟʟᴄᴀ 的电报发送,每行 40 个字符,并由快递员亲自递送。停。
😱 S U M M A R Y 😱
我不知道你能得到比我写的更多的“默认Unicode 🐪”。嗯,是的,我愿意:你也应该使用和。可能还有更多。Unicode::Collate
Unicode::LineBreak
正如你所看到的,有太多的Unicode事情,你真的需要担心,因为存在任何诸如“默认为Unicode”之类的东西。
你会发现,就像我们在 5.8 中🐪所做的那样,根本不可能将所有这些东西强加给从一开始就没有设计好来解释它们的代码。你善意的自私让整个世界都崩溃了。
即使你这样做了,仍然有一些关键问题需要大量的思考才能正确。没有可以翻转的开关。只有大脑,我的意思是真正的大脑,在这里就足够了。你必须学习很多东西。退到手动打字机上,你根本不能指望在无知中偷偷溜走。这是 21 世纪,你不能因为故意的无知而希望 Unicode 消失。
你必须学习它。时期。“一切都能正常工作”永远不会那么容易,因为这将保证很多事情都不起作用——这就使有一种方法可以“让一切正常”的假设无效。
你也许能够为极少数和非常有限的操作获得一些合理的默认值,但并非没有考虑比我认为你所拥有的更多的事情。
仅举一个例子,规范排序会引起一些真正的麻烦。😭 'õ'、'õ'、'ȭ' 和 'ō̃' 都应该与 'õ' 匹配,但你到底要怎么做呢?这比看起来更难,但这是你需要考虑的事情。💣"\x{F5}"
"o\x{303}"
"o\x{303}\x{304}"
"o\x{304}\x{303}"
如果说我对Perl有一点了解的话,那就是它的Unicode位做什么和不做什么,我向你保证:“ ̲ᴛ̲j̲ᴇ̲j̲ᴇ̲ ̲ɪ̲s̲ ̲ɴ̲ᴏ̲ ̲U̲ɴ̲ɪ̲ᴄ̲ᴏ̲ᴅ̲ᴇ̲ ̲ᴍ̲ᴀ̲ɢ̲ɪ̲ᴄ̲ ̲ʙ̲ᴜ̲ʟ̲j̲ᴇ̲ᴛ̲ ̲ ” 😞
你不能只是改变一些默认值,然后一帆风顺。确实,我使用 set to 运行🐪,但仅此而已,即使这主要用于命令行内容。对于真正的工作,我会经历上面概述的所有步骤,而且我非常非常非常仔细地完成。PERL_UNICODE
"SA"
😈 ¡ƨdləɥ ƨᴉɥʇ ədoɥ puɐ ʻλɐp əɔᴉu ɐ əʌɐɥ ʻʞɔnl poo七. 😈
评论
我认为你误解了Unicode及其与Perl的关系。无论您以哪种方式存储数据、Unicode、ISO-8859-1 或许多其他东西,您的程序都必须知道如何将它获得的字节解释为输入(解码)以及如何表示它想要输出的信息(编码)。如果这种解释是错误的,你就会把数据弄得乱七八糟。程序内部没有一些神奇的默认设置来告诉程序之外的东西如何操作。
你认为这很难,很可能,因为你已经习惯了一切都是ASCII。你应该考虑的一切都被编程语言和它必须与之交互的所有东西所忽略。如果所有东西都只使用 UTF-8 并且您别无选择,那么 UTF-8 也同样容易。但并非所有东西都使用 UTF-8。例如,你不希望你的输入句柄认为它正在获得 UTF-8 八位字节,除非它实际上是,并且如果从它们读取的东西不能处理 UTF-8,你不希望你的输出句柄是 UTF-8。Perl没有办法知道这些事情。这就是你是程序员的原因。
我不认为 Perl 5 中的 Unicode 太复杂了。我认为这很可怕,人们会避免它。这是有区别的。为此,我把Unicode放在了《学习Perl》第6版中,在《有效的Perl编程》中也有很多Unicode的东西。您必须花时间学习和理解Unicode及其工作原理。否则,您将无法有效地使用它。
评论
use utf8_everywhere
我们都同意,这是一个困难的问题,原因有很多, 但这正是试图让每个人都更轻松的原因。
CPAN上最近有一个模块utf8::all,它试图“打开Unicode。所有这一切”。
正如已经指出的,你不能神奇地让整个系统(外部程序、外部 Web 请求等)也使用 Unicode,但我们可以共同努力,制作明智的工具,使解决常见问题变得更容易。这就是我们成为程序员的原因。
如果 utf8::all 没有做你认为它应该做的事情,让我们改进它以使其变得更好。或者,让我们制作其他工具,这些工具可以尽可能地满足人们的不同需求。
`
评论
utf8::all
unicode_strings
/u
use charnames ":full"
[a-z]
printf
\n
\R
.
\X
Perl::Critic
unichars -gs '/(?=\P{Ll})\p{Lower}|(?=\P{Lu})\p{Upper}/x' | ucsort --upper | cat -n | less -r
... | ucsort --upper --preprocess='s/(\d+)/sprintf "%#012d", $1/ge'
在阅读此线程时,我经常得到的印象是人们使用“UTF-8”作为“Unicode”的同义词。请区分Unicode的“码位”(ASCII码的放大相对)和Unicode的各种“编码”。其中有一些,其中 UTF-8、UTF-16 和 UTF-32 是当前的,还有一些已经过时了。
请,UTF-8(以及所有其他编码)仅在输入或输出中存在并具有意义。在内部,从 Perl 5.8.1 开始,所有字符串都保留为 Unicode“代码点”。没错,您必须启用之前令人钦佩的一些功能。
评论
处理 Unicode 文本分为两个阶段。首先是“如何在不丢失信息的情况下输入和输出”。第二个是“我如何根据当地语言惯例处理文本”。
Tchrist 的帖子涵盖了两者,但第二部分是他帖子中 99% 的文本的来源。大多数程序甚至无法正确处理 I/O,因此在开始担心规范化和排序规则之前,了解这一点非常重要。
这篇文章旨在解决第一个问题
当你把数据读入Perl时,它并不关心它是什么编码。它分配一些内存并将字节存放在那里。如果你说 ,它只是将这些字节输出到你的终端,它可能设置为假设写入它的所有内容都是 UTF-8,并且你的文本就会显示出来。print $str
奇妙。
除了,它不是。如果您尝试将数据视为文本,您会看到正在发生不好的事情。你只需要看到Perl对你的字符串的看法和你对字符串的看法不一致。写一行,比如:然后输入,你会得到 12...不是正确答案, 4.length
perl -E 'while(<>){ chomp; say length }'
文字化け
这是因为 Perl 假设你的字符串不是文本。你必须告诉它它是文本,然后它才会给你正确的答案。
这很简单;Encode 模块具有执行此操作的功能。通用入口点是(当然是 或)。该函数从外部世界获取一些字符串(我们称之为“八位字节”,一种花哨的说法“8 位字节”),并将其转换为一些 Perl 可以理解的文本。第一个参数是字符编码名称,如“UTF-8”或“ASCII”或“EUC-JP”。第二个参数是字符串。返回值是包含文本的 Perl 标量。Encode::decode
use Encode qw(decode)
(还有 ,它假定编码为 UTF-8。Encode::decode_utf8
如果我们重写我们的单行:
perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'
我们输入文字化け,得到“4”作为结果。成功。
这就是 Perl 中 99% 的 Unicode 问题的解决方案。
关键是,每当任何文本进入您的程序时,您都必须对其进行解码。互联网无法传输字符。文件无法存储字符。数据库中没有字符。只有八位字节,在 Perl 中不能将八位字节视为字符。您必须使用 Encode 模块将编码的八位字节解码为 Perl 字符。
问题的另一半是从程序中获取数据。这很容易;你只需说,决定你的数据将采用什么编码(UTF-8 到理解 UTF-8 的终端,UTF-16 用于 Windows 上的文件等),然后输出结果,而不仅仅是输出。use Encode qw(encode)
encode($encoding, $data)
$data
此操作将 Perl 的字符(即程序运行的字符)转换为可供外界使用的八位字节。如果我们可以通过互联网或终端发送字符,那会容易得多,但我们不能:只有八位字节。因此,我们必须将字符转换为八位字节,否则结果是不确定的。
总而言之:对所有输出进行编码并解码所有输入。
现在我们将讨论使这有点挑战性的三个问题。首先是图书馆。他们是否正确处理文本?答案是......他们尝试。如果您下载网页,LWP 会以文本形式返回您的结果。如果对结果调用正确的方法,那就是(恰好是 ,而不是 ,它只是它从服务器获取的八位字节流。数据库驱动程序可能是片状的;如果您仅将 DBD::SQLite 与 Perl 一起使用,它会奏效,但是如果其他工具将文本存储为 UTF-8 以外的某种编码在您的数据库中......井。。。除非您编写代码来正确处理它,否则它不会被正确处理。decoded_content
content
输出数据通常更容易,但如果你看到“打印中的宽字符”,那么你就知道你在某个地方搞砸了编码。这个警告的意思是“嘿,你正试图向外界泄露Perl字符,这没有任何意义”。你的程序看起来可以工作(因为另一端通常正确地处理原始的Perl字符),但它非常糟糕,随时可能停止工作。用显式 !Encode::encode
第二个问题是 UTF-8 编码的源代码。除非你在每个文件的顶部说,否则 Perl 不会假设你的源代码是 UTF-8。这意味着每次你说类似的话时,你都会向你的程序注入垃圾,这将完全破坏一切。您不必“使用 utf8”,但如果您不这样做,则不得在程序中使用任何非 ASCII 字符。use utf8
my $var = 'ほげ'
第三个问题是Perl如何处理过去。很久以前,还没有Unicode这样的东西,Perl假设一切都是Latin-1文本或二进制文本。因此,当数据进入程序并开始将其视为文本时,Perl 将每个八位字节视为一个拉丁字符。这就是为什么当我们询问“文字化け”的长度时,我们得到了 12 个。Perl 假设我们正在对 Latin-1 字符串 “æååã” (12 个字符,其中一些是非打印的)进行操作。
这被称为“隐式升级”,这是一件完全合理的事情,但如果你的文本不是 Latin-1,这不是你想要的。这就是为什么显式解码输入至关重要的原因:如果你不这样做,Perl就会这样做,而且它可能会做错。
人们会遇到麻烦,因为他们的数据有一半是正确的字符串,而有些仍然是二进制的。Perl 会将仍然是二进制的部分解释为拉丁语 1 文本,然后将其与正确的字符数据组合在一起。这将使它看起来像是正确处理你的角色破坏了你的程序,但实际上,你只是没有修复它。
举个例子:你有一个程序,它读取一个 UTF-8 编码的文本文件,你在每一行上都贴上一个 Unicode,然后你把它打印出来。你这样写:PILE OF POO
while(<>){
chomp;
say "$_ 💩";
}
然后运行一些 UTF-8 编码的数据,例如:
perl poo.pl input-data.txt
它打印 UTF-8 数据,每行末尾都有一个 poo。完美,我的程序有效!
但是不,你只是在做二进制串联。您正在从文件中读取八位字节,删除带有 chomp 的八位字节,然后在字符的 UTF-8 表示形式中附加字节。当您修改程序以解码文件中的数据并对输出进行编码时,您会注意到您得到的是垃圾 (“ð©”) 而不是 poo。这会让你相信解码输入文件是错误的做法。事实并非如此。\n
PILE OF POO
问题是便便被隐式升级为拉丁语-1。如果你要制作文字文本而不是二进制文本,那么它将再次起作用!use utf8
(这是我在帮助人们使用Unicode时看到的第一个问题。他们做对了部分,这打破了他们的计划。这就是未定义结果的可悲之处:你可以有一个工作程序很长一段时间,但当你开始修复它时,它就会坏掉。不用担心;如果您正在向程序中添加编码/解码语句并且它中断,则仅意味着您还有更多工作要做。下次,当你从一开始就考虑到Unicode进行设计时,它会容易得多!
这就是您需要了解的有关Perl和Unicode的全部信息。如果你告诉Perl你的数据是什么,它拥有所有流行的编程语言中最好的Unicode支持。但是,如果你假设它会神奇地知道你正在输入什么样的文本,那么你就会不可撤销地丢弃你的数据。仅仅因为您的程序今天可以在 UTF-8 终端上运行,并不意味着它明天就可以在 UTF-16 编码的文件上运行。因此,现在确保安全,并省去破坏用户数据的麻烦!
处理 Unicode 的简单部分是编码输出和解码输入。困难的部分是找到所有输入和输出,并确定它是什么编码。但这就是为什么你得到大笔钱:)
评论
Encode
open
binmode
open
您应该启用 Unicode 字符串功能,如果您使用 ,这是默认设置。v5.14;
你不应该真正使用 Unicode 标识符,尤其是对于通过 UTF-8 的外来代码,因为它们在 Perl 5 中是不安全的;只有 CPERL 做对了。例如,参见 http://perl11.github.io/blog/unicode-identifiers.html
关于文件句柄/流的 UTF-8:您需要自己决定外部数据的编码。库无法知道这一点,而且由于连 libc 都不支持 UTF-8,因此正确的 UTF-8 数据很少见。还有更多的 WTF-8,即 UTF-8 的 Windows 畸变。
顺便说一句:Moose 并不是真正的“现代 Perl”;他们只是劫持了这个名字。Moose 是完美的 Larry Wall 风格的后现代 Perl 与 Bjarne Stroustrup 风格的混合,一切都很好,对正确的 Perl 6 语法不拘一格,例如,使用字符串作为变量名、可怕的字段语法,以及一个非常不成熟的幼稚实现,它比正确的实现慢 10 倍。
cperl 和 Perl 6 是真正的现代 Perl 实现,其中形式服从功能,并且实现被简化和优化。
评论