为什么现代Perl默认避免使用UTF-8?
我想知道为什么使用Perl构build的大多数现代解决scheme默认情况下都不启用UTF-8 。
我知道核心Perl脚本有很多遗留问题,它们可能会破坏一些东西。 但是从我的观点来看,在21世纪,大的新项目(或者大视angular的项目)应该从头开始对其软件进行UTF-8validation。 我仍然没有看到它发生。 例如, 穆斯启用严格和警告,但不是Unicode 。 Modern :: Perl也减less了样板,但没有UTF-8处理。
为什么? 在2011年的现代Perl项目中是否有避免使用UTF-8的理由?
评论@ tchrist太长了,所以我在这里添加它。
我似乎没有说清楚。 让我尝试添加一些东西。
tchrist和我看到的情况差不多,但是我们的结论是完全相反的。 我同意,Unicode的情况是复杂的,但这就是为什么我们(Perl用户和编码器)需要一些使得UTF-8处理像现在一样容易的层(或杂注)。
特里斯特指出了很多方面,我会在几天甚至几周的时间里阅读和思考它们。 不过,这不是我的观点。 tchrist试图certificate没有一个单一的方式“启用UTF-8”。 我没有那么多的知识来解决这个问题。 所以,我坚持活的例子。
我玩Rakudo和UTF-8只是在那里, 因为我需要 。 我没有任何问题,只是工作。 也许有更深的一些限制,但在开始时,我所testing的所有工作正如我所料。
这不应该成为现代Perl 5的目标吗? 我强调一下:我并不是build议使用UTF-8作为核心Perl的默认字符集,我build议可以为那些开发新项目的人简单地触发它。
另一个例子,但用更负面的语气。 框架应该使开发更容易。 几年前,我尝试了一些网页框架,但只是把它们扔掉了,因为“启用UTF-8”是如此晦涩难懂。 我没有find如何以及在哪里挂钩的Unicode支持。 这是非常耗时的,我发现更容易走老路。 现在我在这里看到了与Mason 2相同的问题: 如何使Mason2 UTF-8变得干净? 。 所以,这是一个相当新的框架,但是使用它与UTF-8需要深入的内部知识。 它就像一个大红色的标志:停下来,不要使用我!
我真的很喜欢Perl。 但处理Unicode是痛苦的。 我仍然发现自己跑在墙上。 某种方式tchrist是正确的,并且回答我的问题:新项目不吸引UTF-8,因为它在Perl 5中太复杂了。
</s>🐪🐫🐪🐫🐪🌞𝕲𝖔𝖆𝖓𝖉𝕯𝖔𝕷𝖎𝖐𝖊𝖜𝖎𝖘𝖊🌞🐪🐫🐪🐁
𝓔𝓭𝓲𝓽:𝙎𝙞𝙢𝙥𝙡𝙚𝙨𝙩℞:𝟕𝘿𝙞𝙨𝙘𝙧𝙚𝙩𝙚𝙍𝙚𝙘𝙤𝙢𝙢𝙚𝙣𝙙𝙖𝙩𝙞𝙤𝙣𝙨
-
将您的
PERL_UNICODE
variables设置为AS
。 这使所有Perl脚本都将@ARGV
解码为UTF-8string,并将stdin,stdout和stderr全部三个编码设置为UTF-8。 这些都是全球效应,而不是词汇效应。 -
在源文件的顶部(程序,模块,库,
do
hickey),突出声明你正在运行perl版本5.12或更好通过:use v5.12; # minimal for unicode string feature
use v5.14; # optimal for unicode string feature
-
启用警告,因为先前的声明只启用狭窄和function,而不是警告。 我也build议将Unicode警告提升为exception,所以请使用这两行,而不仅仅是其中之一。 但请注意,在5.12版本中,
utf8
警告类包含三个可单独启用的其他子警告:nonchar
,surrogate
和non_unicode
。 这些你可能希望施加更大的控制。use warnings;
use warnings qw( FATAL utf8 );
-
声明这个源单元被编码为UTF-8。 虽然曾经有一段时间这个杂志做了其他的事情,但是现在它只为这个唯一的目的服务,没有别的目的:
use utf8;
-
声明在这个词法范围内打开一个文件句柄的任何东西,除非你另有说明,否则假定这个stream是用UTF-8编码的。 这样你不会影响其他模块或其他程序的代码。
use open qw( :encoding(UTF-8) :std );
-
通过
\N{CHARNAME}
启用指定的字符。use charnames qw( :full :short );
-
如果你有一个
DATA
句柄,你必须明确地设置它的编码。 如果你想这是UTF-8,那么说:binmode(DATA, ":encoding(UTF-8)");
当然,最终你会发现自己关心的其他事情还没有结束,但是这样做足以逼近国家的目标:“尽一切努力以UTF-8的方式工作”,尽pipe这些条款有点弱化了。
另外一个编译指示,虽然不是Unicode相关的,但是:
use autodie;
强烈build议。
🎅𝕹𝖔𝕸𝖆𝖌𝖎𝕭𝖚𝖑𝖑𝖊𝖙🎅
说“Perl应该[ 某种程度上! ]在默认情况下启用Unicode“甚至没有开始思考如何在某种罕见和孤立的情况下说出足够的用处。 Unicode不仅仅是一个更大的字符集; 也是这些angular色如何以许多方式相互作用的。
即使是那些(一些)人们认为他们想要的简单的微小措施,也肯定会破坏数百万行代码,而这些代码却没有机会“升级”到新的“ 勇敢的新世界” 。
比人们假装更为复杂。 过去几年来,我一直在想这个问题。 我很想certificate我错了。 但我不认为我是。 Unicode从根本上说比你想强加给它的模型复杂得多,而且在这里复杂的是你永远无法在地毯下扫荡。 如果你尝试,你会破坏你自己的代码或别人的。 在某些时候,你只需要分解和学习Unicode就可以了。 你不能假装它是不是。
🐪使Unicode变得简单,远远超过我曾经使用过的其他任何东西。 如果您认为这样做不好,请尝试其他方法。 然后回到🐪:要么你会回到一个更美好的世界,否则你会把同样的知识带给你,这样我们就可以利用你的新知识来使这些东西变得更好。
</s>𝕴𝖉𝖊𝖆𝖘𝖋𝖔𝖗𝖆⸗𝕬𝖜𝖆𝖗𝖊🐪𝕷𝖆𝖚𝖓𝖉𝖗𝖞𝕷𝖎𝖘𝖙💡
至less,下面是一些对于“默认启用Unicode”所需要的东西,正如你所说的那样:
-
所有的源代码默认都是UTF-8。 你可以
use utf8
或export PERL5OPTS=-Mutf8
。 -
🐪DATA句柄应该是UTF-8。 你将不得不在每个包的基础上做这个,就像
binmode(DATA, ":encoding(UTF-8)")
。 -
对脚本程序的参数默认应该被理解为UTF-8。
export PERL_UNICODE=A
或perl -CA
export PERL5OPTS=-CA
,或export PERL5OPTS=-CA
。 -
标准input,输出和错误stream应该默认为UTF-8。
export PERL_UNICODE=S
的所有这些,或I
,O
和/或E
只是其中的一些。 这就像perl -CS
。 -
除非另有声明,否则由opened开启的任何其他手柄均应视为UTF-8。
export PERL_UNICODE=D
或与i
和o
特定的这些;export PERL5OPTS=-CD
将工作。 这使得所有这些都成为-CSAD
。 -
覆盖两个基础加上你打开的所有stream
export PERL5OPTS=-Mopen=:utf8,:std
。 见uniquote 。 -
你不想错过UTF-8编码错误。 尝试
export PERL5OPTS=-Mwarnings=FATAL,utf8
。 并确保你的inputstream总是binmode
d: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字符不是默认启用的,所以添加
export PERL5OPTS=-Mcharnames=:full,:short,latin,greek
或者其他的。 请参阅uninames和tcgrep 。 -
你几乎总是需要从标准的
Unicode::Normalize
模块的各种types的分解中访问函数。export PERL5OPTS=-MUnicode::Normalize=NFD,NFKD,NFC,NFKD
,然后通过NFD和NFC出站的东西总是运行传入的东西。 没有I / O层,但是我知道,但是看到nfc , nfd , nfkd和nfkc 。 -
使用
eq
,ne
,lc
,cmp
,sort
和&c&cc的string比较总是错误的。 所以,而不是@a = sort @b
,你需要@a = Unicode::Collate->new->sort(@b)
。 不妨将其添加到您的export PERL5OPTS=-MUnicode::Collate
。 您可以caching二进制比较的密钥。 -
🐪内置插件如
printf
和write
Unicode数据做错误的事情。 您需要为前者使用Unicode::GCString
模块 ,并且还要使用Unicode::LineBreak
模块 。 见uwc和unifmt 。 -
如果你想让它们算作整数,那么你将不得不通过
Unicode::UCD::num
函数运行你的\d+
捕获,因为内置的atoi (3)目前不够聪明。 -
您将在文件系统上遇到文件系统问题。 一些文件系统默默执行到NFC的转换; 其他人默默执行转换到NFD。 而其他人还在做其他事情。 有的甚至完全无视这个问题,导致更大的问题。 所以你必须做你自己的NFC / NFD处理,以保持理智。
-
所有你的代码涉及到
az
或AZ
, 必须改变 ,包括m//
,s///
和tr///
。 这应该是一个尖叫的红旗,你的代码被打破了。 但是如何改变还不清楚。 获得正确的财产,并理解他们的casefolds,比你想象的更难。 我每天都用unichars和uniprops 。 -
使用
\p{Lu}
代码与使用[A-Za-z]
代码几乎一样错误。 您需要使用\p{Upper}
,并知道原因。 是的\p{Lowercase}
和\p{Lower}
与\p{Ll}
和\p{Lowercase_Letter}
。 -
使用
[a-zA-Z]
的代码更糟糕。 它不能使用\pL
或\p{Letter}
; 它需要使用\p{Alphabetic}
。 不是所有的字母都是字母,你知道! -
如果您正在使用
/[\$\@\%]\w+/
查找variables,那么您遇到了问题。 您需要查找/[\$\@\%]\p{IDS}\p{IDC}*/
,甚至不会考虑标点variables或包variables。 -
如果你正在检查空白,那么你应该select
\h
和\v
,具体情况取决于。 而且你不应该使用\s
,因为它不代表[\h\v]
,与stream行的观点相反。 -
如果您使用
\n
作为界限,甚至\r\n
,那么您做错了。 你必须使用\R
,这是不一样的! -
如果你不知道什么时候和是否调用Unicode :: Stringprep ,那么你最好学习。
-
不区分大小写的比较需要检查两个事物是否是相同的字母,不pipe它们的变音符号等等。 最简单的方法是使用标准的Unicode :: Collate模块。
Unicode::Collate->new(level => 1)->cmp($a, $b)
。 也有eq
方法等等,你也许应该学习match
和substr
方法。 这些内置插件具有明显的优势。 -
有时这还不够,而且你需要Unicode :: Collate :: Locale模块来代替
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", " ð")
是错误的。 同样,如果你不使用语言环境,或者如果你使用英语的话,那么“ae”和“æ”就是eq
,但是它们在冰岛语言环境中是不同的。 怎么办? 这很难,我告诉你。 你可以玩ucsort来testing一些这些东西了。 -
考虑如何匹配string“ niño ”中的模式CVCV(辅音,元音,辅音,元音)。 它的NFDforms – 你已经记得好好记得把它放进去 – 变成“nin'o o”。 现在你要做什么? 即使假装元音是
[aeiou]
(顺便说一句,这是错误的),你也将无法像(?=[aeiou])\X)
,因为即使在NFD中, ø' 不分解 ! 但是,使用我刚刚给你看的UCA比较,它会等于一个“o”。 你不能依靠NFD,你必须依靠UCA。
💩𝔸𝕤𝕤𝕦𝕖𝕖𝔹𝕠𝕖𝕟𝕟𝕖𝕤𝕤💩💩
而这还不是全部。 人们对Unicode做了百万个错误的假设。 在他们理解这些事情之前,他们的密码将被打破。
-
假定它可以打开文本文件而不指定编码的代码被破坏。
-
采用默认编码的代码是某种本地平台编码被破坏的。
-
假定日文或中文网页占用UTF-16空间less于UTF-8的代码是错误的。
-
假设Perl在内部使用UTF-8的代码是错误的。
-
假定编码错误总会引发exception的代码是错误的。
-
假设Perl代码点限制为0x10_FFFF的代码是错误的。
-
假定您可以将
$/
设置$/
与任何有效的行分隔符一起工作的代码是错误的。 -
如果
lc(uc($s)) eq $s
或uc(lc($s)) eq $s
假设往返相等的代码假设是完全错误的。 考虑到uc("σ")
和uc("ς")
都是"Σ"
,但是lc("Σ")
不可能返回这两者。 -
假定每个小写代码点都有一个明确的大写字母的代码被破坏了,反之亦然。 例如,
"ª"
是不带大写的小写字母; 而"ᵃ"
和"ᴬ"
都是字母,但不是小写字母; 但是,它们都是小写的代码点,没有相应的大写版本。 了解? 他们不是\p{Lowercase_Letter}
,尽pipe是\p{Letter}
和\p{Lowercase}
\p{Letter}
\p{Lowercase}
。 -
假定更改大小写的代码不会改变string的长度被破坏。
-
假定只有两种情况的代码被破坏。 还有titlecase。
-
假定只有字母的代码有破坏的情况。 除了信件,事实certificate,数字,符号,甚至标志都有案例。 事实上,改变案例甚至可以改变其主要的一般类别,例如
\p{Mark}
变成\p{Letter}
。 它也可以使其从一个脚本切换到另一个脚本。 -
假定这种情况从不依赖于语言环境的代码被破坏了。
-
假定Unicode的代码给出了有关POSIX语言环境的图。
-
假设你可以删除变音符号来获取基本的ASCII字母的代码是邪恶的,仍然是破坏的,脑残的,错误的和死刑的理由。
-
假设变音符号
\p{Diacritic}
和mark\p{Mark}
是相同的东西的代码被破坏了。 -
假设
\p{GC=Dash_Punctuation}
覆盖了\p{Dash}
已损坏的代码。 -
假设短划线,连字符和负数的代码是彼此相同的,或者只有一个是错误的。
-
假定每个代码点所占用的代码不超过一个打印列被破坏。
-
假定所有
\p{Mark}
字符占用零个打印列的代码已损坏。 -
假设看起来相似的字符相同的代码被破坏了。
-
假设不相似的字符不相似的代码被破坏。
-
假定只有一个
\X
可匹配的行中的代码点数量有限的代码是错误的。 -
假定
\X
无法以\p{Mark}
字符开头的代码是错误的。 -
假定
\X
永远不能拥有两个非\p{Mark}
字符的代码是错误的。 -
假定它不能使用
"\x{FFFF}"
是错误的。 -
假定非BMP代码点需要两个UTF-16(代理)代码单元的代码将编码为两个单独的UTF-8字符,每个代码单元一个是错误的。 它不会:它编码为单码点。
-
如果在产生的UTF-8的开始部分放置一个BOM,那么从UTF-16或UTF-32转换到UTF-8的代码将被破坏。 这是如此愚蠢的工程师应该删除他们的眼皮。
-
假设CESU-8的代码是有效的UTF编码是错误的。 同样,认为将U + 0000编码为
"\xC0\x80"
是UTF-8也是错误的。 这些家伙也值得眼睑治疗。 -
假定字符如
>
代码总是指向右侧,而且总是指向左侧是错误的,因为它们实际上并不是这样。 -
如果您首先输出字符
X
,然后输出字符Y
,那么这些代码会显示为XY
是错误的代码。 有时候他们没有。 -
假定ASCII码足以正确书写英文的代码是愚蠢的,短视的,文盲的,破坏的,邪恶的和错误的。 与他们的头! 如果这看起来太极端了,我们可以妥协:从今以后,他们只能用一只脚的大脚趾敲打(其余的仍然被禁止)。
-
假定所有
\p{Math}
代码点都是可见字符的代码是错误的。 -
假定
\w
仅包含字母,数字和下划线的代码是错误的。 -
假定
^
和~
是标点符号的代码是错误的。 -
假设有变音符号的代码是错误的。
-
相信诸如
₨
包含任何字母的代码是错误的。 -
相信
\p{InLatin}
的代码与\p{Latin}
被严重破坏的代码相同。 -
那些认为
\p{InLatin}
几乎永远有用的代码几乎肯定是错误的。 -
相信给定
$FIRST_LETTER
作为某个字母表中的第一个字母而$LAST_LETTER
作为同一个字母表中的最后一个字母,[${FIRST_LETTER}-${LAST_LETTER}]
具有任何含义的代码几乎总是完整的错误和错误无意义的。 -
相信某人的名字只能包含某些字符的代码是愚蠢的,冒犯的和错误的。
-
试图将Unicode还原为ASCII的代码不仅是错误的,它的执行者也不应再被允许再次编程。 期。 我甚至没有积极的态度,他们甚至应该被允许再次看到,因为到目前为止它显然还没有做的很好。
-
相信有一些假装文本文件编码不存在的方法的代码被破坏并且是危险的。 也可以戳另一只眼睛。
-
将未知字符转换成的代码
?
是破碎,愚蠢,braindead,并运行违反标准的build议,说不要这样做! RTFM为什么不。 -
相信它可以可靠猜测未标记的文本文件的编码的代码是一个疯狂的混乱和天真的混乱,只有来自宙斯的闪电将修复。
-
相信您可以使用
printf
宽度填充和validationUnicode数据的代码是错误的。 -
相信一旦你用给定的名字成功创build了一个文件的代码,当你在它的封装目录上运行
ls
或者readdir
时,你会发现你创build的文件的名字是错误的,错误的和错误的。 不要为此感到惊讶! -
相信UTF-16是一个固定宽度编码的代码是愚蠢的,破碎的和错误的。 撤销他们的编程许可。
-
代码从一个平面的代码点不同于任何其他平面的代码是事实上的错误和错误。 回到学校。
-
相信像
/s/i
这样的东西只能匹配"S"
或"s"
是错误的。 你会感到惊讶。 -
使用
\PM\pM*
代替使用\X
来查找字形集群的代码被破坏并且是错误的。 -
应该全心全意地鼓励那些想回到ASCII世界的人们,为了荣耀的升级,他们应该为所有的数据input需求提供免费的电动手动打字机。 发送给他们的信息应该通过一个电报每行40个字符发送,并由信使交付。 停。
</s>🐪𝕭𝖔𝖎𝖑𝖊𝖗⸗𝖕𝖑𝖆𝖙𝖊𝖋𝖔𝖗𝖀𝖓𝖎𝖈𝖔𝖉𝖊⸗𝕬𝖜𝖆𝖗𝖊𝕮𝖔𝖉𝖊🐪🎁
我自己的样板往往是这样的:
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 stackdumped exceptions # *unless* we're in an try block, in which # case just generate a clucking stackdump instead local $SIG{__WARN__} = sub { if ($^S) { cluck "Trapped warning: @_" } else { confess "Deadly warning: @_" } }; while (<>) { chomp; $_ = NFD($_); ... } continue { say NFC($_); } __END__
</s>𝕾𝖀𝕸𝕸𝕬𝕽𝖄</s>
我不知道你能得到比我写的更多的“缺省的Unicode”。 那么,是的,我这样做:你应该使用Unicode::Collate
和Unicode::LineBreak
。 也许更多。
正如你所看到的,有太多的Unicode的东西,你真的不必担心那里存在任何“默认为Unicode”的东西。
正如我们在5.8版中所做的那样,你将会发现,把所有这些东西强加在从一开始就没有被devise好的代码上是不可能的。 你的善意自私刚刚打破了整个世界。
即使你这样做了,仍然有一些关键的问题需要大量的思考才能得到解决。 没有开关可以翻转。 除了大脑,我的意思是真正的大脑 ,在这里就足够了。 有很多东西你必须学习。 把模具撤回到手动打字机,你根本无法指望在无知中潜行。 这是21世纪,你不能因为故意的无知而希望Unicode。
你必须学习它。 期。 “万事俱备”永远不会那么容易,因为这样可以保证很多事情都不起作用,这就使得“永不成功”的假设失效了。
对于less数几个非常有限的操作,你或许可以得到一些合理的默认值,但不是没有考虑到比我想象的更多的东西。
只是一个例子,规范sorting会导致一些真正的麻烦。 😭 "\x{F5}"
'O', "o\x{303}"
'O', "o\x{303}\x{304}"
'ȭ',和"o\x{304}\x{303}"
'ō'都应该匹配'õ' ,但是在这个世界上你将如何去做? 这比看起来更难,但是这是你需要考虑的。 💣
如果有一点我知道关于Perl,那就是它的Unicode位做什么和不做什么,我向你保证: “ᴛʜᴇʀᴇɪsɴᴏUɴɪᴄᴏᴅᴇᴍᴀɢɪᴄʙᴜʟʟᴇᴛ”
你不能只是改变一些默认值,并得到顺利的航行。 这是真的,我运行🐪与PERL_UNICODE
设置为"SA"
,但是这一切,甚至是主要是命令行的东西。 对于真正的工作,我会经历上面列出的所有步骤,而且我非常仔细地做。
😈😈d😈əɥɥƨᴉɥʇəɥɥɥɐɐɐɐɐ😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈😈
处理Unicode文本有两个阶段。 首先是“我怎样才能input和输出它,而不会丢失信息”。 第二个是“如何根据当地的语言惯例来对待文本”。
tchrist的post涵盖了两者,但是第二部分是他post中99%的文字来自哪里。 大多数程序甚至不能正确处理I / O,因此在开始担心标准化和整理之前,了解这一点很重要。
这篇文章旨在解决第一个问题
当你将数据读入Perl时,它并不关心它是什么编码。 它分配一些内存并在那里存储字节。 如果你说print $str
,它只是将这些字节输出到你的terminal,这可能是假设所有写到它的东西都是UTF-8,你的文本就显示出来了。
奇妙。
除此之外,不是。 如果您尝试将数据视为文本,则会看到“正在发生错误”。 你只需要去看看Perl对你的string的看法,以及你对string的看法。 写一个类似于: perl -E 'while(<>){ chomp; say length }'
的一行, perl -E 'while(<>){ chomp; say length }'
perl -E 'while(<>){ chomp; say length }'
并键入文字化け
,你会得到12 …不正确的答案,4。
这是因为Perl假定你的string不是文本。 你必须告诉它,这是文本之前,它会给你正确的答案。
这很容易, 编码模块具有这样的function。 通用入口点是Encode::decode
(或者use Encode qw(decode)
,当然)。 这个函数需要一些来自外部世界的string(我们称之为“八位字节”,这是8位字节的一种说法),然后把它转换成一些Perl可以理解的文本。 第一个参数是字符编码名称,如“UTF-8”或“ASCII”或“EUC-JP”。 第二个参数是string。 返回值是包含文本的Perl标量。
(还有Encode::decode_utf8
,它使用UTF-8编码。)
如果我们重写我们的单行版本:
perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'
我们input文字化け,得到“4”。 成功。
那就在这里,解决了Perl中99%的Unicode问题。
关键是,每当任何文本进入你的程序,你必须解码它。 互联网不能传输字符。 文件不能存储字符。 数据库中没有字符。 只有八位字节,你不能把八位字节视为Perl中的字符。 您必须使用Encode模块将编码的八位字节解码为Perl字符。
问题的另一半是从您的程序中获取数据。 这很容易, 你只需要use Encode qw(encode)
,决定你的数据的编码是什么(UTF-8是理解UTF-8的terminal,Windows的文件是UTF-16等等),然后输出encode($encoding, $data)
的结果encode($encoding, $data)
而不是只输出$data
。
这个操作把Perl的字符转换成可以被外部世界使用的八位字节。 如果我们能够通过互联网或者我们的terminal发送字符,那将会容易得多,但是我们不能:只有八位字节。 所以我们必须将字符转换为八位字节,否则结果是不确定的。
总结:编码所有输出并解码所有input。
现在我们将讨论三个问题,这使得这个问题有点困难。 首先是图书馆。 他们处理文本是否正确? 答案是…他们尝试。 如果你下载了一个网页,LWP会把你的结果作为文本返回给你。 如果你对结果调用正确的方法,那就是(这恰好是decoded_content
,而不是content
,它只是从服务器获得的八位字节stream)。数据库驱动程序可以是片状的; 如果你使用DBD :: SQLite只用Perl,它会工作,但如果其他工具已经把文本存储为UTF-8以外的一些编码在你的数据库…呃…它不会被正确处理直到你编写正确的代码来处理它。
输出数据通常比较容易,但是如果你看到“宽字符在打印”,那么你知道你正在搞乱编码的地方。 这个警告的意思是“嘿,你试图把Perl的angular色泄露给外部世界,这没有任何意义”。 你的程序似乎工作(因为另一端通常正确处理原始的Perl字符),但它是非常破碎,并可能在任何时候停止工作。 修复它显式Encode::encode
!
第二个问题是UTF-8编码的源代码。 除非您在每个文件的顶部use utf8
,否则Perl不会认为您的源代码是UTF-8。 这意味着,每当你说出像my $var = 'ほげ'
这样的东西时,你就会向你的程序中注入垃圾,这将彻底地破坏所有的东西。 您不必“使用utf8”,但如果您不使用,则不得在程序中使用任何非ASCII字符。
第三个问题是Perl如何处理过去。 很久以前,没有Unicode的东西,Perl认为一切都是Latin-1文本或二进制。 所以当数据进入你的程序并开始把它当作文本处理的时候,Perl会把每个八位字节看作一个拉丁字母1。 这就是为什么当我们询问“文字化け”的长度时,我们得到了12个。Perl认为我们正在使用Latin-1string“æååã”(这是12个字符,其中有些是非打印的)。
这被称为“隐式升级”,这是一个完全合理的做法,但如果您的文本不是Latin-1,则不是您想要的。 这就是为什么明确解码input至关重要:如果你不这样做,Perl会,而且可能会做错。
人们遇到麻烦,一半的数据是一个合适的string,有些仍然是二进制的。 Perl会将仍然是二进制的部分解释为Latin-1文本,然后将其与正确的字符数据组合。 这会让你看起来好像处理你的angular色正确地破坏了你的程序,但实际上,你还没有把它修好就够了。
下面是一个例子:你有一个程序读取一个UTF-8编码的文本文件,然后在每一行上添加一个PILE OF POO
,然后打印出来。 你这样写:
while(<>){ chomp; say "$_ 💩"; }
然后运行一些UTF-8编码的数据,如:
perl poo.pl input-data.txt
它在每行的末尾用poo打印UTF-8数据。 完美,我的程序有效!
但是不,你只是在做二进制连接。 您正在从文件中读取八位字节,使用chomp删除\n
,然后添加PILE OF POO
字符的UTF-8表示中的字节。 当你修改你的程序来解码文件中的数据并对输出进行编码时,你会注意到你得到了垃圾(“ð©”)而不是poo。 这会使你相信解码input文件是错误的。 不是。
问题在于,这个大便被隐含地升级为拉丁一号。 如果你use utf8
来创build文本文本而不是二进制文件,那么它将再次工作!
(这是我在帮助Unicode的人时看到的头号问题,他们做得不错,打破了他们的程序,这对于未定义的结果感到难过:你可以有一个长时间的工作程序,但是当你开始修复时,它会中断,不要担心,如果你在你的程序中join编码/解码语句,它会中断,这意味着你有更多的工作要做,下一次,当你从头开始deviseUnicode时,它会是更容易!)
这就是所有你需要了解Perl和Unicode的知识。 如果你告诉Perl你的数据是什么,它在所有stream行的编程语言中具有最好的Unicode支持。 如果你认为它会奇迹般地知道你在喂什么样的文本,那么你将会不可挽回地垃圾你的数据。 仅仅因为你的程序今天在你的UTF-8terminal上工作并不意味着它将在UTF-16编码文件上工作。 因此,现在就安全起来,为自己省去捣毁用户数据的头痛!
处理Unicode的简单部分是编码输出和解码input。 困难的部分是find你所有的input和输出,并确定它是哪个编码。 但这就是为什么你得到大块钱:)
我们都同意这个问题很多,原因很多,但是这正是让每个人都更容易的原因。
在CPAN上有一个最近的模块, utf8 :: all ,它试图“打开Unicode。所有”。
正如已经指出的那样,你不能神奇地使整个系统(外部程序,外部networking请求等)使用Unicode,但是我们可以一起工作,制作出能够使常见问题更容易的合理工具。 这就是我们是程序员的原因。
If utf8::all doesn't do something you think it should, let's improve it to make it better. Or let's make additional tools that together can suit people's varying needs as well as possible.
`
I think you misunderstand Unicode and its relationship to Perl. No matter which way you store data, Unicode, ISO-8859-1 , or many other things, your program has to know how to interpret the bytes it gets as input (decoding) and how to represent the information it wants to output (encoding). Get that interpretation wrong and you garble the data. There isn't some magic default setup inside your program that's going to tell the stuff outside your program how to act.
You think it's hard, most likely, because you are used to everything being ASCII. Everything you should have been thinking about was simply ignored by the programming language and all of the things it had to interact with. If everything used nothing but UTF-8 and you had no choice, then UTF-8 would be just as easy. But not everything does use UTF-8. For instance, you don't want your input handle to think that it's getting UTF-8 octets unless it actually is, and you don't want your output handles to be UTF-8 if the thing reading from them can handle UTF-8. Perl has no way to know those things. That's why you are the programmer.
I don't think Unicode in Perl 5 is too complicated. I think it's scary and people avoid it. There's a difference. To that end, I've put Unicode in Learning Perl, 6th Edition , and there's a lot of Unicode stuff in Effective Perl Programming . You have to spend the time to learn and understand Unicode and how it works. You're not going to be able to use it effectively otherwise.
While reading this thread, I often get the impression that people are using " UTF-8 " as a synonym to " Unicode ". Please make a distinction between Unicode's "Code-Points" which are an enlarged relative of the ASCII code and Unicode's various "encodings". And there are a few of them, of which UTF-8, UTF-16 and UTF-32 are the current ones and a few more are obsolete.
Please, UTF-8 (as well as all other encodings ) exists and have meaning in input or in output only. Internally, since Perl 5.8.1, all strings are kept as Unicode "Code-points". True, you have to enable some features as admiringly covered previously.
There's a truly horrifying amount of ancient code out there in the wild, much of it in the form of common CPAN modules. I've found I have to be fairly careful enabling Unicode if I use external modules that might be affected by it, and am still trying to identify and fix some Unicode-related failures in several Perl scripts I use regularly (in particular, iTiVo fails badly on anything that's not 7-bit ASCII due to transcoding issues).