为什么现代的Perl默认情况下会避免使用UTF-8?


557

我想知道为什么大多数使用Perl构建的现代解决方案默认情况下都不启用UTF-8

我知道核心Perl脚本有很多遗留问题,可能会破坏事情。但是,从我的角度来看,在21 的世纪,新的大项目(或具有大的方面讲项目)应该从头开始他们的软件UTF-8的证明。我仍然看不到它的发生。例如,Moose启用严格和警告,但不启用UnicodeModern :: Perl也减少了样板,但没有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中它太复杂了。


15
对不起,但我同意@tchrist - UTF-8非常难。没有框架或工具可以“翻转开关”然后正确处理它。这是您在设计应用程序时必须直接考虑的事情,而不是任何类型的框架或语言都可以为您处理的事情。如果rakudo恰好对您有用,那么您对测试用例的探索就不够多了-因为它将使用@tchrist的答案中的几个示例,然后是屠夫。
比利·奥尼尔

12
您究竟希望Moose或Modern :: Perl做什么?神奇地再次将文件和数据库中的随机编码字符数据转换为有效数据吗?
jrockway

13
这意味着什么?驼鹿与文本操作无关。为什么要了解字符编码,更不用说为您选择默认编码了?(无论如何,您列出的编译指示不接触编码的原因是因为该约定是为了使Perl编译指示影响词法行为。假设整个世界(包括其他模块)是UTF-8,这完全是错误的事情(这里不是PHP或Ruby。)
jrockway 2011年

8
(还...“大多数现代Perl应用程序”在UTF-8上中断了吗?我当然从来没有写过应用程序,无论是Perl还是其他方式,那都不是Unicode干净的。)
jrockway 2011年

11
Nb。tchrist(Tom Christiansen)发表了有关Unicode的[ training.perl.com/OSCON2011/index.html Tom Christiansen的OSCON 2011资料]。标题为“ Unicode支持大战:好,坏和(主要是)丑陋”的文章讨论了不同编程语言中的Unicode支持。只有Google Go和Perl5支持完整的Unicode,只有Google Go内置(不提及Perl6)。
JakubNarębski2011年

Answers:


1146

𝙎𝙞𝙢𝙥𝙡𝙚𝙨𝙩 :𝟕𝘿𝙞𝙨𝙘𝙧𝙚𝙩𝙚𝙍𝙚𝙘𝙤𝙢𝙢𝙚𝙣𝙙𝙖𝙩𝙞𝙤𝙣𝙨

  1. 将您的PERL_UNICODE变量设置为AS。这使所有Perl脚本都解码@ARGV为UTF-8字符串,并将stdin,stdout和stderr的全部三个的编码设置为UTF-8。这些都是全局效应,而不是词汇效应。

  2. 在源文件(程序,模块,库,dohickey)的顶部,可以通过以下方式明确声明您正在运行Perl版本5.12或更高版本:

    use v5.12;  # minimal for unicode string feature
    use v5.14;  # optimal for unicode string feature
  3. 启用警告,因为先前的声明仅启用结构和功能,而不启用警告。我还建议将Unicode警告提升为异常,因此请同时使用这两行,而不仅仅是其中之一。不过请注意,根据v5.14,该utf8警告类包括都可以单独启用其他三个subwarnings: ,noncharsurrogatenon_unicode。您可能希望对这些施加更大的控制权。

    use warnings;
    use warnings qw( FATAL utf8 );
  4. 声明此源单元被编码为UTF-8。尽管很久以前,这种实用主义做过其他事情,但现在它仅满足一个单一的目的,而没有其他目的:

    use utf8;
  5. 声明在此词法范围内但不在其他范围内打开文件句柄的任何事物均假定该流以UTF-8进行编码,除非另有说明。这样,您就不会影响其他模块或其他程序的代码。

    use open qw( :encoding(UTF-8) :std );
  6. 通过启用命名字符\N{CHARNAME}

    use charnames qw( :full :short );
  7. 如果有DATA句柄,则必须显式设置其编码。如果要将其设置为UTF-8,请说:

    binmode(DATA, ":encoding(UTF-8)");

当然,您最终可能会遇到其他事情,但这些事情没有尽头,但是尽管这些术语的含义有些减弱,但足以满足“使所有事物都适用UTF-8”的国家目标。

尽管与Unicode不相关,但另一种编译指示是:

      use autodie;

强烈建议。

🐪🐫🐪🌴🌞 𝕲𝖔𝕿𝖍𝖔𝖚𝖆𝖓𝖉𝕯𝖔𝕷𝖎𝖐𝖊𝖜𝖎𝖘𝖊 🌞🐪🐫🐪🐁


𝕭𝖔𝖎𝖑𝖊𝖗⸗𝖕𝖑𝖆𝖙𝖊𝖋𝖔𝖗🐪🐪


这些天我自己的样板看起来像这样:

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__

𝕸𝕸𝖙𝖙𝖙𝖙𝖙𝖙


说“ Perl应该[ 以某种方式!]“默认情况下启用Unicode”甚至还没有开始考虑说出足够多的信息,甚至在某种罕见和孤立的情况下几乎没有用。Unicode不仅仅是一个更大的字符库;这也是这些角色如何以多种方式相互作用的方式。

甚至(某些)人似乎认为他们想要的头脑简单的最小措施也可以保证彻底破坏数百万行代码,这些代码没有机会“升级”到您出色的全新《勇敢的新世界》现代性。

这比人们假装的方式更复杂。在过去的几年中,我已经考虑过很多。希望证明我错了。但是我不认为我是。Unicode从根本上比您要强加给它的模型更为复杂,而且这里存在着复杂性,您永远无法扫清障碍。如果尝试,则会破坏您自己的代码或他人的代码。在某个时候,您只需要分解并了解Unicode的含义。您不能假装这不是事实。

竭尽全力使Unicode变得简单,这比我用过的其他任何东西都要多。如果您认为这样不好,请尝试一段时间。然后回到🐪:要么您将回到一个更美好的世界,要么您将带给您相同的知识,以便我们可以利用您的新知识使🐪在这些方面变得更好。


𝖋𝖔𝖗𝖀𝖓𝖎𝖈𝖔𝖉𝖊𝕷𝖎𝖘𝖙𝕷𝖎𝖘𝖙𝕷𝖎𝖘𝖙


至少,🐪似乎需要某些东西才能“默认启用Unicode”,如您所说:

  1. 默认情况下,所有🐪源代码都应使用UTF-8。您可以使用use utf8或来获得export PERL5OPTS=-Mutf8

  2. 🐪 DATA句柄应为UTF-8。您必须按照打包方式进行操作,如中所述binmode(DATA, ":encoding(UTF-8)")

  3. 🐪脚本的程序参数默认情况下应理解为UTF-8。 export PERL_UNICODE=Aperl -CAexport PERL5OPTS=-CA

  4. 标准输入,输出和错误流应默认为UTF-8。export PERL_UNICODE=SIO和/或E仅其中一些。这就像perl -CS

  5. 除非另有声明,否则由opened打开的任何其他句柄都应视为UTF-8。export PERL_UNICODE=D或与io为这些特定的一些; export PERL5OPTS=-CD会工作。这-CSAD对他们所有人来说都是如此。

  6. 覆盖这两个基础以及您打开时使用的所有流export PERL5OPTS=-Mopen=:utf8,:std。参见uniquote

  7. 您不想错过UTF-8编码错误。尝试export PERL5OPTS=-Mwarnings=FATAL,utf8。并确保您的输入流始终binmode指向:encoding(UTF-8),而不仅仅是:utf8

  8. 🐪应该将介于128–255之间的代码点理解为对应的Unicode代码点,而不仅仅是不正确的二进制值。 use feature "unicode_strings"export PERL5OPTS=-Mfeature=unicode_strings。这将使uc("\xDF") eq "SS""\xE9" =~ /\w/。一个简单的export PERL5OPTS=-Mv5.12或更好的也可以做到这一点。

  9. 默认情况下,不启用命名的Unicode字符,因此请添加export PERL5OPTS=-Mcharnames=:full,:short,latin,greek或类似的字符。参见uninamestcgrep

  10. 您几乎总是需要从标准Unicode::Normalize模块的各种分解类型访问功能。 export PERL5OPTS=-MUnicode::Normalize=NFD,NFKD,NFC,NFKD,然后始终通过NFD运行传入内容,并从NFC中运行传出内容。我尚不知道这些的I / O层,但请参阅nfcnfdnfkdnfkc

  11. 在🐪字符串比较使用eqnelccmpsort,&C&CC永远是错的。因此@a = sort @b,您需要的是@a = Unicode::Collate->new->sort(@b)。最好将其添加到您的文件中export PERL5OPTS=-MUnicode::Collate。您可以缓存密钥以进行二进制比较。

  12. 🐪内建喜欢Unicode数据printfwrite做错了事。您需要对前者使用Unicode::GCString模块,并且对后者也要使用Unicode::LineBreak模块。参见uwcunifmt

  13. 如果您希望它们以整数形式计数,那么您将不得不\d+通过Unicode::UCD::num函数运行捕获因为🐪的内置atoi(3)当前不够聪明。

  14. 您将在👽文件系统上遇到文件系统问题。一些文件系统无声地强制转换为NFC。其他人则默默地强制转换为NFD。其他人还在做其他事情。有些甚至完全忽略了这个问题,这导致了更大的问题。因此,您必须自己进行NFC / NFD处理以保持理智。

  15. 所有你🐪代码涉及a-zA-Z与此类必须改变,包括m//s///,和tr///。它应该是一个醒目的红色标志,表明您的代码已损坏。但是尚不清楚它必须如何改变。要获得正确的属性并了解其案例,比您想象的要难。我使用单字符uniprops每一天。

  16. 使用的代码\p{Lu}几乎与使用的代码一样错误[A-Za-z]。您需要\p{Upper}改用,并知道原因。是的,\p{Lowercase}\p{Lower}来自不同的\p{Ll}\p{Lowercase_Letter}

  17. 使用的代码[a-zA-Z]更糟。而且它不能使用\pL\p{Letter}; 它需要使用\p{Alphabetic}。您知道,并非所有字母都是字母!

  18. 如果您要查找带有的🐪变量/[\$\@\%]\w+/,那么您会遇到问题。您需要查找/[\$\@\%]\p{IDS}\p{IDC}*/,即使那样也没有考虑标点符号变量或包变量。

  19. 如果要检查空格,则应在\h和之间进行选择\v,具体取决于。而且,您不应该使用\s它,因为它 [\h\v]符合人们的普遍看法。

  20. 如果您要使用\n线边界或什至\r\n,那么您做错了。您必须使用\R,这是不一样的!

  21. 如果您不知道何时以及是否调用Unicode :: Stringprep,那么最好学习一下。

  22. 不区分大小写的比较需要检查两个事物是否是相同的字母,无论它们的变音符号如何。最简单的方法是使用标准Unicode :: Collat​​e模块。Unicode::Collate->new(level => 1)->cmp($a, $b)。也有eq方法之类的,您也应该了解matchsubstr方法。与🐪内置组件相比,它们具有明显的优势。

  23. 有时这还不够,您需要改为使用Unicode :: Collat​​e :: 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", " ð")错误的。同样,eq如果您不使用语言环境,或者使用英语,则使用“ ae”和“æ” ,但是在冰岛语言环境中它们是不同的。怎么办?我告诉你,这很艰难。您可以使用 ucsort来测试其中的一些内容。

  24. 考虑如何匹配字符串“ niño ” 中的模式CVCV(辅音,元音,辅音,元音)。您最好记住的NFD格式已记为“ nin \ x {303} o”。现在你要做什么?即使假装一个元音[aeiou](顺便说一句,这是错误的),您也将无法执行(?=[aeiou])\X)任何一项操作,因为即使在NFD中,“ø”之类的代码点也不会分解!但是,使用我刚刚向您显示的UCA比较,它将测试等于“ o”。您不能依靠NFD,而必须依靠UCA。


𝕤𝕤💩𝕤𝕤💩𝕤𝕤𝕤𝕤


不仅如此。人们对Unicode做出了一百万个错误的假设。在他们理解这些内容之前,他们的🐪代码将被破坏。

  1. 假定它可以打开文本文件而不指定编码的代码已损坏。

  2. 假定默认编码是某种本机平台编码的代码已损坏。

  3. 假定日文或中文网页在UTF-16中比UTF-8占用更少空间的代码是错误的。

  4. 假定Perl内部使用UTF-8的代码是错误的。

  5. 假定编码错误将始终引发异常的代码是错误的。

  6. 假定Perl代码点限制为0x10_FFFF的代码是错误的。

  7. 假定您可以将其设置$/为可以与任何有效的行分隔符一起使用的代码是错误的。

  8. 假设case折叠上的往返相等的代码(例如lc(uc($s)) eq $suc(lc($s)) eq $s)完全是错误的。考虑到uc("σ")uc("ς") 都是"Σ",但lc("Σ")不可能返回两者。

  9. 假定每个小写代码点都有一个不同的大写字母的代码,反之亦然。例如,"ª"是一个小写字母,没有大写字母;而"ᵃ"和和"ᴬ"均为字母,但不是小写字母;但是,它们都是小写代码点,没有对应的大写版本。知道了?他们没有 \p{Lowercase_Letter},尽管是两\p{Letter}\p{Lowercase}

  10. 假定大小写不变的代码不会改变字符串的长度。

  11. 假定只有两种情况的代码已损坏。还有标题栏。

  12. 假定仅字母为大写的代码已损坏。除了字母,事实证明数字,符号甚至标记都有大小写。实际上,更改大小写甚至可以更改其主要常规类别,例如\p{Mark}变成\p{Letter}。它还可以使它从一个脚本切换到另一个脚本。

  13. 假定大小写从不依赖于语言环境的代码将被破坏。

  14. 假定Unicode给出关于POSIX语言环境的数字的代码已损坏。

  15. 假定您可以删除变音符号以获取基本ASCII字母的代码是邪恶的,静止的,​​残破的,损坏大脑的,错误的以及死刑的理由。

  16. 假定变音符号\p{Diacritic}和标记\p{Mark}相同的代码已损坏。

  17. 假定的代码\p{GC=Dash_Punctuation}覆盖了\p{Dash}损坏的所有部分。

  18. 假设破折号,连字符和减号彼此相同,或者每个破折号都是错误的,则该代码为假。

  19. 假定每个代码点占用不超过一个打印列的代码已损坏。

  20. 假定所有\p{Mark}字符占用零个打印列的代码已损坏。

  21. 即假设其外形相似的字符代码一样坏。

  22. 代码假定其不字一样看起来是一样坏。

  23. 假设一行中只有一个\X可以匹配的代码点数量有限制的代码是错误的。

  24. 假定\X永远不能以\p{Mark}字符开头的代码是错误的。

  25. 假定\X永远不能容纳两个非\p{Mark}字符的代码是错误的。

  26. 假定它不能使用的代码"\x{FFFF}"是错误的。

  27. 假定需要两个UTF-16(代理)代码单元的非BMP代码点的代码将编码为两个单独的UTF-8字符(每个代码单元一个)是错误的。它没有:它编码为单个代码点。

  28. 如果将带有BOM的UTF-16或UTF-32转换为UTF-8的代码被破坏,则该代码会被破坏。这太愚蠢了,工程师应该去掉他们的眼皮。

  29. 假设CESU-8是有效的UTF编码的代码是错误的。同样,认为将U + 0000编码"\xC0\x80"为UTF-8的代码是错误的。这些家伙也应该接受眼睑治疗。

  30. 假定像>总是指向右边而<始终指向左边这样的字符的代码是错误的,因为它们实际上并非如此。

  31. 假设您先输出字符X然后输出character的代码Y将显示为XY错误。有时他们没有。

  32. 假定ASCII足以正确编写英语的代码是愚蠢的,近视的,不识字的,残破的,邪恶的和错误的。 抬起头来!如果那看起来太极端了,我们可以妥协:从今以后,他们只能用一只脚的大脚趾打字。(其余将用胶带粘贴。)

  33. 假定所有\p{Math}代码点都是可见字符的代码是错误的。

  34. 假定\w仅包含字母,数字和下划线的代码是错误的。

  35. 假定为^并且~是标点符号的代码是错误的。

  36. 假定ü有变音符号的代码是错误的。

  37. 认为诸如此类内容中包含任何字母的代码是错误的。

  38. 认为\p{InLatin}\p{Latin}被严重破坏的代码相同。

  39. 认为\p{InLatin}几乎有用的代码几乎肯定是错误的。

  40. 认为以给定$FIRST_LETTER的字母$LAST_LETTER作为某个字母的第一个字母和该字母的最后一个字母的代码[${FIRST_LETTER}-${LAST_LETTER}]几乎总是完整的,错误的,无意义的。

  41. 认为某人的名字只能包含某些字符的代码是愚蠢,令人反感和错误的。

  42. 试图将Unicode转换为ASCII的代码不仅是错误的,而且永远不应允许其肇事者再次进行编程。期。我什至不能肯定他们甚至应该被允许再次见面,因为到目前为止,这显然并没有给他们带来什么好处。

  43. 认为有某种方法可以假装不存在文本文件编码的代码已损坏并且很危险。最好也将另一只眼睛戳出来。

  44. 将未知字符转换?为残破,愚蠢,脑残的代码,其运行与标准建议相反,该标准建议不要执行该操作RTFM为什么不这样。

  45. 认为可以可靠地猜测未标记文本文件的编码的代码,是犯有狂妄自大和无礼的致命混杂,只有宙斯的闪电才能解决。

  46. 相信您可以使用🐪 printf宽度来填充和证明Unicode数据损坏和错误的代码。

  47. 相信一旦成功使用给定名称创建文件,该代码将在您运行lsreaddir在其所在目录中时发现,使用该文件创建的名称实际上是错误的,损坏的和错误的。不要为此感到惊讶!

  48. 认为UTF-16是固定宽度编码的代码是愚蠢的,损坏的和错误的。撤销他们的编程许可证。

  49. 对待一个平面的代码点与其他平面的代码点的区别对待的代码事实上是错误的。回到学校。

  50. 认为诸如此类的东西/s/i只能匹配"S"或被"s"破坏或错误的代码。您会感到惊讶。

  51. 代码,使用\PM\pM*查找字形集群,而不是用\X坏了,错了。

  52. 应当全心全意地鼓励想要回到ASCII世界的人们这样做,并且为了纪念他们的光辉升华,应该为他们提供所有数据输入需求的免费电动手动打字机。发送给他们的消息应通过电报以每行40个字符的形式发送,并由快递员手动发送。停。


𝖀𝕸𝖄𝖄


我不知道您可以得到多少“ written中的默认Unicode”比我写的更多。嗯,是我做的:你应该使用Unicode::CollateUnicode::LineBreak。可能还有更多。

正如你看到的,有太多太多的Unicode的东西,你真的担心为了有存在的任何这样的事情“默认为Unicode”。

正如我们在🐪5.8中所做的那样,您将发现,将所有这些内容强加于一开始就没有考虑到它们的代码上是完全不可能的。您善意的自私行为破坏了整个世界。

即使您这样做了,仍然存在一些关键问题,需要进行大量思考才能正确解决。没有可以翻转的开关。除了大脑,我的意思是真正的大脑就足够了。您必须学习很多东西。Modulo退缩到手动打字机,您根本不能指望无知而潜行。这是21世纪,您不能希望Unicode会因故意的无知而消失。

您必须学习它。期。“一切正常”绝非易事,因为这将确保许多事情不会发生工作-这无效假设有可能永远是一个方法来“做这一切的工作。”

您可能能够为很少的非常有限的操作获得一些合理的默认值,但并非没有比我想的要多的事情。

仅举一个例子,规范的排序会引起一些真正的麻烦。😭'õ "\x{F5}" '"o\x{303}" 'õ'"o\x{303}\x{304}" 'ȭ'"o\x{304}\x{303}" 'ō̃'都应与'õ'匹配,但是您打算怎么做呢?这比看起来要难,但这是您需要考虑的。💣

如果我对Perl有所了解,那就是它的Unicode位做什么和不做什么,我向你保证: 😞

您不能只更改一些默认设置并顺利进行。的确,我运行🐪时PERL_UNICODE将set设置为"SA",但是仅此而已,甚至大部分用于命令行。对于真实的工作,我会经历上面列出的所有许多步骤,并且我会非常非常仔细地做到这一点。


lɥɥɥɥɥɐl l l nlpoo⅁😈


56
就像Sherm Pendley指出的那样:“全部!”。如果我今天写新东西,那么UTF-8应该是完成工作的最简单方法。它不是。您的样板证明了这一点。并非每个人都有这样的知识,可以将许多翻转开关转到正确的位置。抱歉,我今天辛苦辛苦了,所以明天我将在示例中更多地评论主要文章。

17
通过阅读上面的列表,一个结论应该显而易见:不要大写。只是不要。曾经 计算上很昂贵,并且语义主要取决于“语言环境”尝试识别的内容。
Tim Bray)的

72
我是唯一发现讽刺的是,tchrist的这篇文章在FF / Chrome / IE / Opera上呈现出如此巨大的差异,有时甚至难以辨认的吗?
Damageboy

15
虽然我通常喜欢这篇文章,并且赞不绝口,但有一件事使我烦恼。有很多“代码被破坏了”。尽管我不同意这一说法,但我认为证明其破绽是件好事。这样一来,它就可以从(从答案的那部分)遍历蚂蚁到教育。

36
@xenoterracide不,我没有故意使用有问题的代码点;这是让您安装George Douros的超棒Symbola字体(涵盖Unicode 6.0)的设计图。de @depesz这里没有空位来解释为什么每个破碎的假设都是错误的。@leonbloy 很多这样的东西通常适用于Unicode,而不仅仅是Perl。某些材料可能会在十月份发布的4 Programming Perl🐪,第4版中显示。🎃我还有一个月的时间来研究它,而Unicode在那里。正则表达式,太
tchrist

96

处理Unicode文本有两个阶段。第一个是“如何输入和输出它而不丢失信息”。第二个是“如何根据本地语言约定来处理文本”。

tchrist的帖子涵盖了两者,但第二部分是其帖子中99%的文字来自何处。大多数程序甚至都无法正确处理I / O,因此在开始担心规范化和归类之前应了解这一点很重要。

这篇文章旨在解决第一个问题

当您将数据读入Perl时,它并不关心它是什么编码。它分配一些内存并将字节存储在那里。如果你说print $str,它只是将那些字节弄污到您的终端上,这很可能是假定写入的所有内容都是UTF-8,并且您的文本出现了。

奇妙。

除了,不是。如果您尝试将数据视为文本,则会发现发生了不好的事情。您只需length要看一下Perl对字符串的看法和对字符串的看法是不同的即可。像这样写一线:perl -E 'while(<>){ chomp; say length }'并输入文字化け,您将得到12 ...不是正确的答案,4。

这是因为Perl假定您的字符串不是文本。您必须先告诉它是文本,然后才能为您提供正确的答案。

这很容易;编码模块具有执行此功能的功能。通用入口点是Encode::decode(或use Encode qw(decode),当然)。该函数从外界获取一些字符串(我们称其为“八位字节”,一种称呼“ 8位字节”的方式),并将其转换为Perl可以理解的一些文本。第一个参数是字符编码名称,例如“ UTF-8”或“ ASCII”或“ EUC-JP”。第二个参数是字符串。返回值是包含文本的Perl标量。

(还有Encode::decode_utf8,它假定编码为UTF-8。)

如果我们重写单线代码:

perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'

我们输入文字化け并得到“ 4”作为结果。成功。

在那里,这就是Perl中99%Unicode问题的解决方案。

关键是,每当程序中出现任何文本时,都必须对其进行解码。互联网无法传输字符。文件不能存储字符。您的数据库中没有字符。只有八位位组,在Perl中不能将八位位组视为字符。您必须使用Encode模块将编码的八位位组解码为Perl字符。

问题的另一半是从程序中获取数据。这很容易;您只需要说一下use Encode qw(encode),确定数据的编码方式(对于理解UTF-8的终端为UTF-8,对于Windows上的文件为UTF-16,等等),然后输出结果encode($encoding, $data)而不是仅输出$data

此操作将Perl的字符(您的程序对其进行操作)转换为可由外界使用的八位字节。如果我们仅可以通过Internet或终端发送字符,就会容易得多,但不能:仅八位字节。因此,我们必须将字符转换为八位字节,否则结果是不确定的。

总结一下:对所有输出进行编码,并对所有输入进行解码。

现在,我们将讨论使这变得有些挑战的三个问题。首先是图书馆。他们是否正确处理文字?答案是...他们尝试。如果您下载网页,LWP将以文字形式返回您的结果。如果在结果上调用正确的方法,那就是(恰好是decoded_content,而不是content,这只是它从服务器获取的八位位组流。)数据库驱动程序可能很不稳定。如果仅将Perl与DBD :: SQLite一起使用,它将可以解决问题,但是如果某些其他工具将文本以UTF-8以外的其他编码形式存储在数据库中...那么...它将无法正确处理直到您编写代码以正确处理它为止。

输出数据通常比较容易,但是如果您看到“打印中的宽字符”,那么您就知道自己在某个地方弄乱了编码。该警告的意思是“嘿,您正试图将Perl角色泄漏给外界,这没有任何意义”。您的程序似乎可以正常工作(因为另一端通常可以正确处理原始Perl字符),但是该程序已损坏,随时可能停止工作。用一个明确的解决它Encode::encode

第二个问题是UTF-8编码的源代码。除非您use utf8在每个文件的顶部都说,否则Perl不会假定您的源代码为UTF-8。这意味着每次您说类似的内容时my $var = 'ほげ',您都在向程序中注入垃圾,这将彻底破坏所有内容。你不必为“使用UTF8”,但如果你不这样做,你一定不能在程序中使用任何非ASCII字符。

第三个问题是Perl如何处理过去。很久以前,还没有Unicode这样的东西,Perl认为所有东西都是Latin-1文本或二进制。因此,当数据进入程序并开始将其视为文本时,Perl会将每个八位位组视为Latin-1字符。这就是为什么当我们要求输入“文字化け”的长度时,我们得到12。Perl假定我们使用的是拉丁文1字符串“æååã”(这是12个字符,其中一些是非打印字符)。

这被称为“隐式升级”,这是完全合理的事情,但是如果您的文字不是Latin-1,就不是您想要的。这就是为什么对输入进行显式解码至关重要的原因:如果您不这样做,Perl会这样做,而且这样做可能会出错。

人们遇到麻烦,因为他们一半的数据是正确的字符串,而有些仍然是二进制的。Perl会将仍然是二进制的部分解释为Latin-1文本,然后将其与正确的字符数据合并。这将使您看起来像正确处理您的字符会破坏您的程序,但实际上,您还没有对它进行足够的修复。

这是一个示例:您有一个程序读取UTF-8编码的文本文件,将Unicode固定PILE OF POO到每一行,然后将其打印出来。您将其编写为:

while(<>){
    chomp;
    say "$_ 💩";
}

然后在一些UTF-8编码的数据上运行,例如:

perl poo.pl input-data.txt

它在每行的结尾打印带有小便的UTF-8数据。完美,我的程序有效!

但是不,您只是在进行二进制连接。您正在从文件中读取八位位组,\n使用chomp 删除a ,然后在字符的UTF-8表示形式中添加PILE OF POO字节。修改程序以解码文件中的数据并对输出进行编码时,您会发现收到的是垃圾(“ð©”),而不是便便。这会让您相信解码输入文件是错误的事情。不是。

问题在于,便便已隐式升级为latin-1。如果您use utf8使用文字文本而不是二进制文本,那么它将再次起作用!

(这是我在帮助Unicode使用者时遇到的第一个问题。他们做了正确的事,这破坏了他们的程序。这就是未定义结果的遗憾:您可以长期使用一个工作程序,但是当您开始对其进行修复时,不用担心;如果您在程序中添加了编码/解码语句而又中断了,那仅意味着您还有更多工作要做。下次,当您从一开始就考虑Unicode时,它将容易得多!)

这就是您真正需要的有关Perl和Unicode的全部知识。如果您告诉Perl您的数据是什么,那么它在所有流行的编程语言中都具有最好的Unicode支持。但是,如果您假设它会神奇地知道您要输入的是哪种文本,那么您将无可挽回地浪费数据。仅仅因为您的程序今天可以在UTF-8终端上运行并不意味着它明天就可以在UTF-16编码文件上运行。因此,请立即使其安全,并避免浪费用户数据的麻烦!

处理Unicode的最简单的部分是编码输出和解码输入。困难的部分是找到所有输入和输出,并确定它是哪种编码。但这就是为什么你能赚大钱的原因:)


对该原理进行了很好的解释,但是缺少用于I / O的实际方法。明确地使用该Encode模块非常繁琐且容易出错,这使得阅读有关I / O的代码确实很痛苦。I / O层在需要时可以透明地进行编码和解码,因此提供了一种解决方案。openbinmode考虑其规范,然后pragma open设置默认值,如tchrist在他的回答中建议的那样。
Palec

48

我们都同意,这是一个困难的问题,其原因有很多,但这正是试图使每个人都更轻松的原因。

CPAN上最近有一个模块utf8 :: all,它试图“打开Unicode。全部”。

正如已经指出的那样,您不能神奇地使整个系统(外部程序,外部Web请求等)也使用Unicode,但是我们可以共同努力,使明智的工具更容易解决常见问题。这就是我们成为程序员的原因。

如果utf8 :: all没有做您认为应该做的事情,请对其进行改进以使其更好。或者让我们制作其他工具,使其尽可能地满足人们的不同需求。

`


5
我在引用的模块中看到了很多改进余地utf8::all。它写在unicode_strings功能之前,由FɪɴᴀʟʟʏᴀᴛLᴏɴɢLᴀsᴛ修复正则表达式以在其/u上加一个。我不认为这会引发编码错误的异常,而这正是您真正必须拥有的。它不会加载到use charnames ":full"尚未完成的编译指示中。它不会发出警告[a-z],例如,printf使用\n代替\R.代替字符串宽度\X,但这也许只是一个Perl::Critic问题。如果是我,我要加𝐍𝐅𝐃加𝐍𝐅𝐂加。
tchrist 2011年

13
@tchrist utf8 :: all的问题跟踪器在这里。 github.com/doherty/utf8-all/issues 他们很想听听您的建议。
Schwern 2011年

4
@Schwern:ᴇɴᴏᴛᴜɪbuts,但是请随意窃取和捏我在这里写的东西。老实说,我仍在/正在学习可以做什么与应该做什么以及在哪里进行。这是卸载分拣的一个很好的例子: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'也可能非常好,我也不想为他们做出其他决定。我仍在构建Unicode工具箱
tchrist 2011年

35

我认为您误解了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及其工作方式。否则,您将无法有效使用它。


3
我认为您有意思:这很可怕。应该是吗 对我来说,是Unicode祝福,不是在Perl5中使用它(我不假设任何东西都是ASCII,我的母语至少需要iso8859-4)。我安装了Rakudo,使用UTF-8(在此有限的沙箱中)尝试的所有东西都可以立即使用。我错过了什么?我再强调一遍:可以对Unicode进行微调,这是很好的,但是在大多数时候,都不需要这样做。为了消除对主题的恐惧,一种方法是每个人都读很多东西以了解内部原理。其他:我们有特殊的用法,因此use utf8_everywhere使人们感到高兴。为什么不最后一个?

3
我仍然认为您错过了重点。什么有效?您不需要了解内部原理。您需要了解外部因素,以及如何处理具有不同编码和相同字符表示形式的字符串。再次阅读汤姆的建议。他说的大多数我打赌您会发现Rakudo无法为您解决。
brian d foy

1
@wk:再次阅读Randy的答案。他已经告诉过您有什么限制。
brian d foy

2
@brian d foy:我认为这些限制很好,就像tchrist所说的那样,每个方面都没有神奇的子弹(我承认:在这里问这个问题之前,我没有看到其中的大多数)。因此,当我们使用诸如utf8 :: all之类的内容涵盖许多基本内容时,没有必要每个人都建立自己的巨大样板以使utf8处理的基础知识正常工作。我说“一点都不害怕”:每个人都可以在知道基本知识的情况下开始自己的项目。是的,您是对的,仍然有很多问题。但是当开始变得容易时,我们将有更多的人参与解决这些问题。恕我直言
wk

1
@wk-唯一带有“ utf8:all”或“ uni :: perl”的“错误”只是一个-它们不在CORE中-因此每个人都必须从CPAN安装它。如果您认为这不大的话处理-反思请-是的,这是比较容易使用UTF8与助手模块没有它,Perl核心仍然有Unicode支持。 -但大部分-多复杂,这是错误的。
jm666

28

在阅读此线程时,我经常给人的印象是人们使用“ UTF-8 ”作为“ Unicode ” 的同义词。请区分Unicode的“代码点”和Unicode的各种“编码”,Unicode是ASCII代码的放大的相对形式。其中有一些,其中UTF-8,UTF-16UTF-32是当前的,还有一些已过时。

请使用UTF-8(以及所有其他编码),并且仅在输入或输出中有意义。在内部,从Perl 5.8.1开始,所有字符串都保留为Unicode“代码点”。没错,您必须启用先前介绍的某些功能。


19
我同意人们经常将Uɴɪᴄᴏᴅᴇ与UTF-8⧸16⧸32混淆,但是从根本上和批评上来说,Uɴɪᴄᴏᴅᴇ只是相对于ᴀsᴄɪɪ而言的某种扩大的字符集,这是不正确的最多不过是短短的10-6年Uɴɪᴄᴏᴅᴇ包含更多内容排序
tchrist 2011年

15
@tchrist:第一步是将数据放入程序中并发送到外界,而不会浪费数据。那么您就可以担心排序规则,大小写折叠,字形变体等等。
jrockway 2011年

7
我同意,让perl不丢弃输入或输出必须是第一要务。我想要的是一个可以体现以下虚拟对话的模块或编译指示:“-亲爱的Perl。对于此程序,所有输入和输出将全部为UTF-8。请您不要破坏我的数据吗?-因此,您只能说UFT-8-是吗-是-真的,可以肯定吗-绝对是-您接受如果我提供非UTF-8数据可能会表现出奇怪的行为-是的,很好。 - 好吧。”
hlovdal

10

在野外确实有大量令人恐惧的古老代码,其中大部分以通用CPAN模块的形式出现。我发现如果我使用可能受其影响的外部模块,必须非常小心地启用Unicode,并且仍在尝试找出并修复我经常使用的多个Perl脚本中的一些与Unicode相关的故障(特别是iTiVo失败)由于转码问题,对非7位ASCII的任何内容都严重影响)。


我的意思是使用该-C选项来确保Perl与Unicode是在同一页上,因为即使我明确设置$LANG$LC_ALL正确执行了,我仍然让它决定使用ISO 8859/1而不是Unicode 。(这实际上可能反映了平台语言环境库中的错误。)不管是什么,我都无法在其中带有重音的程序上使用iTivo,这非常令人讨厌,因为完成工作的Perl脚本会因转换错误而失败。
geekosaur 2011年

3
-C没有选项的孤独者容易出错并且容易出错。你打破世界。将PERL5OPT变量设置为-C,您将明白我的意思。我们在v5.8中尝试了这种方法,这真是一场灾难。您不能,也不能不告诉那些不期望它的程序,现在他们正在处理Unicode,无论是否喜欢它。还有安全问题。至少,print while <>如果传递二进制数据,则任何操作都会中断。所有数据库代码也是如此。这是一个可怕的主意。
tchrist 2011年

1
实际上,我是在一般性地讲,不是-C没有选择地讲。我一直在使用的特定调用是-CSDA。就是说,我在5.8.x上呆了很长时间(您好MacPorts ...),所以也许这是其中的一部分。
geekosaur,2011年

1
我在将PERL_UNICODE设置为SA的情况下运行。您CAN NOT将其设置为D.
tchrist

@tchrist:一些Perl varmint已经发布了显示-CSDA和PERL_UNICODE = SDA用法的代码。请利用您在社区中的影响力。他必须停下来!
阿什利(Ashley)

1

您应该启用unicode字符串功能,如果使用v5.14,这是默认设置。

您不应该真正使用Unicode标识符esp。通过utf8获取外来代码,因为它们在perl5中是不安全的,只有cperl做到了。参见例如http://perl11.org/blog/unicode-identifiers.html

关于文件处理/流的utf8:您需要自己确定外部数据的编码。库不知道这一点,并且由于甚至libc都不支持utf8,因此很少有正确的utf8数据。还有更多wtf8,即utf8的窗户畸变。

顺便说一句:驼鹿不是真正的“现代Perl”,他们只是劫持了这个名字。Moose是完美的Larry Wall风格的后现代perl与Bjarne Stroustrup风格的完美结合,适当的perl6语法存在折衷的畸变,例如,使用字符串作为变量名,可怕的字段语法以及非常不成熟的天真实现,比ath慢10倍正确实施。cperl和perl6是真正的现代perls,其形式遵循功能,并且简化并优化了实现。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.