为什么现代Perl默认避免UTF-8 ?

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

我知道核心Perl脚本有许多遗留问题,可能会破坏一些东西。但是,从我的角度来看,在21世纪,大型新项目(或具有大视角的项目)应该从头开始使他们的软件实现UTF-8证明。但我还是不认为会发生这种情况。例如,驼鹿启用严格和警告,但Unicode不启用。现代:Perl也减少了样板文件,但没有UTF-8处理。

为什么?在2011年的现代Perl项目中,是否有一些避免使用UTF-8的理由?


评论@tchrist太长了,所以我把它加在这里。

看来我没有说清楚。让我试着补充一些东西。

tchrist和我看到的情况非常相似,但我们的结论完全相反。我同意,Unicode的情况是复杂的,但这就是为什么我们(Perl用户和编码员)需要一些层(或pragma),使UTF-8的处理像现在一样简单。

tchrist指出了很多方面,我会阅读并思考几天甚至几周。不过,这不是我的重点。tchrist试图证明“启用UTF-8”的方法不止一种。我没有那么多的知识与之争论。所以,我坚持用活生生的例子。

我玩了Rakudo和UTF-8只是在那里在我需要的时候。我没有遇到任何问题,一切都很顺利。也许有一些更深层次的限制,但在开始时,我所测试的所有工作都符合我的预期。

这难道不应该成为现代perl 5的一个目标吗?我要强调一点:我并不是建议将UTF-8作为核心Perl的默认字符集,我建议开发项目的人可以使用啪的一声来触发它。

又是一个例子,但是语气比较消极。框架应该使开发更容易。几年前,我尝试过web框架,但因为“启用UTF-8”太晦涩难懂而放弃了它们。我不知道如何以及在哪里挂钩Unicode支持。这太费时了,所以我发现走老路更容易。现在我看到这里有一个赏金来处理相同的问题梅森 2: 如何使Mason2 UTF-8干净?< / >。因此,它是一个相当新的框架,但是在UTF-8中使用它需要深入了解其内部结构。这就像一个巨大的红色标志:停止,不要利用我!

我真的很喜欢Perl。但是处理Unicode是痛苦的。我仍然发现自己在撞墙。在某种程度上tchrist是正确的,并回答了我的问题:新项目不吸引UTF-8,因为它在Perl 5中太复杂了。

109692 次浏览

在野外有大量的古老代码,其中大部分以公共CPAN模块的形式存在。我发现,如果我使用可能受Unicode影响的外部模块,我必须非常小心地启用Unicode,并且仍然试图在我经常使用的几个Perl脚本中识别和修复一些与Unicode相关的错误(特别是,iTiVo在任何不是7位ASCII的东西上都严重失败,这是由于转码问题)。

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

  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. Enable warnings, since the previous declaration only enables strictures and features, not warnings. I also suggest promoting Unicode warnings into exceptions, so use both these lines, not just one of them. Note however that under v5.14, the utf8 warning class comprises three other subwarnings which can all be separately enabled: nonchar, surrogate, and non_unicode. These you may wish to exert greater control over.

    use warnings;
    use warnings qw( FATAL utf8 );
    
  4. Declare that this source unit is encoded as UTF‑8. Although once upon a time this pragma did other things, it now serves this one singular purpose alone and no other:

    use utf8;
    
  5. Declare that anything that opens a filehandle within this lexical scope but not elsewhere is to assume that that stream is encoded in UTF‑8 unless you tell it otherwise. That way you do not affect other module’s or other program’s code.

    use open qw( :encoding(UTF-8) :std );
    
  6. Enable named characters via \N{CHARNAME}.

    use charnames qw( :full :short );
    
  7. If you have a DATA handle, you must explicitly set its encoding. If you want this to be UTF‑8, then say:

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

There is of course no end of other matters with which you may eventually find yourself concerned, but these will suffice to approximate the state goal to “make everything just work with UTF‑8”, albeit for a somewhat weakened sense of those terms.

One other pragma, although it is not Unicode related, is:

      use autodie;

强烈推荐。

🌴🐪🐫🐪🌞# eyz0🌞🐪🐫🐪🐁


🎁🐪𝕭𝖔𝖎𝖑𝖊𝖗⸗𝖕𝖑𝖆𝖙𝖊𝖋𝖔𝖗𝖀𝖓𝖎𝖈𝖔𝖉𝖊⸗𝕬𝖜𝖆𝖗𝖊𝕮𝖔𝖉𝖊🐪🎁


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

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 utf8export 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=S表示全部,IO和/或E表示部分。这是perl -CS

  5. 除非另有声明,否则由🐪打开的任何其他句柄都应被视为UTF-8;export PERL_UNICODE=Dio对于这些特定的;export PERL5OPTS=-CD可以。这使得它们都是-CSAD

  6. 覆盖两个基地加上你用export PERL5OPTS=-Mopen=:utf8,:std打开的所有流。参见uniquote

  7. 您不想错过UTF-8编码错误。# EYZ0试试。确保你的输入流始终是binmoded到: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层,我知道,但看到nfcnfdnfkd,和nfkc

  11. 在🐪中使用eqnelccmpsort, &c&cc进行字符串比较总是错误的。所以你需要@a = Unicode::Collate->new->sort(@b)而不是@a = sort @b。不妨把它加到你的export PERL5OPTS=-MUnicode::Collate中。可以缓存该键以进行二进制比较。

  12. printfwrite这样的🐪内置程序对Unicode数据做了错误的事情。你需要使用 Unicode::GCString模块为前者,也 Unicode::LineBreak模块为后者。参见uwcunifmt

  13. 如果你想要他们算作整数,那么你将不得不运行你的\d+捕捉通过 Unicode::UCD::num函数,因为🐪的内置atoi(3)目前还不够聪明。

  14. 您将在👽文件系统上遇到文件系统问题。一些文件系统会默默地执行到NFC的转换;另一些则默默地执行到NFD的转换。还有一些人还在做别的事情。有些人甚至完全忽略了这个问题,这导致了更大的问题。所以你必须自己处理NFC/NFD才能保持清醒。

  15. 所有涉及a-zA-Z和此类必须更改的🐪代码,包括m//s///tr///。它应该突出作为一个尖叫的红旗,您的代码是坏的。但目前尚不清楚它必须如何改变。获得正确的属性,并理解它们的案例,比你想象的要难。我使用unicharsuniprops每一天。

  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::Collate模块。# EYZ0。还有eq方法等,您可能也应该了解matchsubstr方法。这些都有明显的优势,🐪内置。

  23. 有时这还不够,你需要用来代替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来测试这些东西。

  24. 考虑如何匹配字符串“niño”中的模式CVCV(辅音,元音,辅音,元音)。它的ndf形式——你最好记得把它放在里面——变成了“nin\x{303}o”。现在你要做什么?即使假装元音是[aeiou](顺便说一句,这是错误的),你也不能做(?=[aeiou])\X)这样的事情,因为即使在NFD中,像' ø ' 这样的代码点也不能分解!但是,使用我刚才展示的UCA比较,它将测试为' o '。你不能依赖NFD,你必须依赖UCA。


💩 𝔸𝕤𝕤𝕦𝕞𝕖𝔹𝕣𝕠𝕜𝕖𝕟𝕟𝕖𝕤𝕤 💩


这还不是全部。人们对统一码有无数错误的假设。在他们理解这些事情之前,他们的🐪代码将会被破坏。

  1. 假定它可以在不指定编码的情况下打开文本文件的代码是坏的。

  2. 假定默认编码是某种本地平台编码的代码被破坏了。

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

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

  5. 假定编码错误总是会引发异常的代码是错误的。

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

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

  8. 假设案例折叠上的往返相等的代码,如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. 假定not看起来相似的字符not相似的代码被破坏。

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

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

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

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

  27. 如果代码假设非bmp代码点需要两个UTF-16(代理)代码单元,则编码为两个单独的UTF-8字符,每个代码单元一个字符,则是错误的。它不会:它编码为单个码点。

  28. 如果将一个BOM放在结果的UTF-8的开头,那么从UTF- 16或UTF- 32转换为UTF-8的代码将被破坏。这太愚蠢了,工程师应该把他们的眼皮割掉。

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

  30. 如果代码假设像>这样的字符总是指向右边,而<总是指向左边,那是错误的——因为它们实际上不是这样的。

  31. 如果您首先输出字符X,然后输出字符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. 将未知字符转换为?的代码是坏的,愚蠢的,无脑的,并且与标准建议相悖,即NOT to DO that ! RTFM for why not。

  45. 相信自己可以可靠地猜测一个未标记文本文件的编码的代码犯了狂妄的致命mélange和只有来自宙斯的闪电才能修复的naïveté。

  46. 认为可以使用🐪printf宽度填充和证明Unicode数据的代码是错误的。

  47. 这些代码认为,一旦您成功地通过给定名称创建了一个文件,当您在其封闭目录上运行lsreaddir时,您实际上会发现您创建它的名称下的文件是错误的、坏的和错误的。不要对此感到惊讶!

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

  49. 处理来自一个平面的码点与来自其他平面的码点稍有不同的代码是ipso facto坏的和错误的。回学校去吧。

  50. 认为像/s/i这样的东西只能匹配"S""s"的代码是错误的。你会大吃一惊的。

  51. 使用\PM\pM*而不是\X查找字母素簇的代码是错误的。

  52. 应该全心全意地鼓励那些想要回到ASCII世界的人这样做,为了纪念他们的光荣升级,应该为他们提供一台前电动手动打字机,以满足他们所有的数据输入需求。发送给他们的信息必须通过ᴀʟʟᴄᴀᴘs电报发送,每行40个字符,并由快递员亲自递送。停止。


😱𝕾𝖀𝕸𝕸𝕬𝕽𝖄😱


我不知道你能得到的“🐪中的默认Unicode”比我写的多多少。好吧,是的,我知道:你应该使用Unicode::CollateUnicode::LineBreak,太。甚至可能更多。

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

你将会发现,就像我们在🐪5.8中所做的那样,把所有这些东西强加到没有从一开始就设计好的代码上是不可能的。你善意的自私毁了整个世界。

即使你这样做了,仍然有一些关键的问题需要大量的思考才能解决。没有你可以拨动的开关。只有大脑,我的意思是真正的大脑,在这里就足够了。你要学的东西太多了。除了退回到手动打字机,你根本不能指望在无知中偷偷摸摸。这是21ˢᵗ世纪,你不能因为故意的无知而希望Unicode消失。

你必须学会。时期。“一切都能正常工作”永远不会那么容易,因为这将保证的许多东西都能正常工作——这使“有一种方法可以使一切正常工作”的假设失效。

您可能能够在非常少且非常有限的操作中获得一些合理的默认值,但并非没有考虑到比我认为的更多的事情。

仅举一个例子,规范排序将会引起一些真正的麻烦。😭"\x{F5}" “o”"o\x{303}" “o”"o\x{303}\x{304}" “ȭ”"o\x{304}\x{303}" “ō̃”都应该匹配“o”,但在世界上你要怎么做呢?这比看起来要难,但这是你需要考虑的事情。💣

如果说我对Perl有什么了解的话,那就是它的Unicode位能做什么,不能做什么,我向您保证:“̲ᴛ̲ʜ̲ᴇ̲ʀ̲ᴇ̲̲ɪs̲̲̲ɴ̲ᴏ̲̲̲Uɴ̲ɪ̲ᴄ̲ᴏ̲ᴅ̲ᴇ̲̲ᴍ̲ᴀ̲ɢ̲ɪ̲ᴄ̲̲ʙ̲ᴜ̲ʟ̲ʟ̲ᴇ̲ᴛ̲̲”😞

你不能仅仅改变一些默认值就一帆风顺。的确,我在运行🐪时将PERL_UNICODE设置为"SA",但仅此而已,即使这样也主要用于命令行。在实际工作中,我经历了上面列出的所有步骤,并且非常非常谨慎。


😈¡ƨdləɥƨᴉɥʇə做ɥpuɐʻλɐpəɔᴉuɐəʌɐɥʻʞɔ问粪便⅁😈

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

由于很多原因,我们都同意这是一个困难的问题, 但这正是我们努力让每个人都更轻松的原因

CPAN上最近有一个模块use utf8::,它试图“打开Unicode”。所有的一切”。

正如已经指出的那样,您不能神奇地让整个系统(外部程序、外部web请求等)也使用Unicode,但是我们可以一起工作,创建合理的工具,使解决常见问题变得更容易。这就是我们成为程序员的原因。

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

在阅读这篇文章时,我经常有这样的印象,人们使用“utf - 8”作为“Unicode”的同义词。请区分Unicode的“码点”,即ASCII码的放大版,以及Unicode的各种“编码”。还有一些,其中UTF-8, utf - 16utf - 32是目前使用的,还有一些已经过时了。

请注意,UTF-8(以及所有其他编码)仅在输入或输出中存在并具有意义。在内部,从Perl 5.8.1开始,所有字符串都保留为Unicode“代码点”。的确,您必须启用前面提到的一些特性。

处理Unicode文本有两个阶段。第一个问题是“我如何在不丢失信息的情况下输入和输出它”。第二个问题是“如何根据当地语言惯例处理文本”。

Tchrist的文章涵盖了这两部分,但他文章中99%的文字都来自第二部分。大多数程序甚至不能正确地处理I/O,因此在开始担心规范化和排序规则之前,理解这一点非常重要。

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

当您将数据读入Perl时,它并不关心它是什么编码。它分配一些内存并将字节存储在那里。如果您说print $str,它只是将这些字节传输到您的终端,它可能设置为假定写入它的所有内容都是UTF-8,然后显示您的文本。

太不可思议了。

但事实并非如此。如果您试图将数据视为文本,您将看到发生了一些不好的事情。只需查看length,就会发现Perl对字符串的看法与您对字符串的看法不一致。写一行代码,比如:perl -E 'while(<>){ chomp; say length }',然后输入文字化け,得到12…不是正确答案,4。

这是因为Perl假定您的字符串不是文本。你必须告诉它这是文本,它才会给你正确的答案。

这很简单;Encode模块有这样做的函数。通用的入口点是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, UTF-16在Windows上的文件,等等),然后输出encode($encoding, $data)的结果,而不是仅仅输出$data

此操作将程序所操作的Perl字符转换为可供外部世界使用的八字节。如果我们能通过互联网或终端发送字符,那就简单多了,但我们不能:只能发送八字节。所以我们必须将字符转换为八字节,否则结果是没有定义的。

总结一下:编码所有输出,解码所有输入。

现在我们将讨论三个让这个问题变得有点挑战性的问题。首先是图书馆。它们是否正确处理文本?答案是……他们试一试。如果你下载了一个网页,LWP会以文本的形式给你结果。如果对结果调用正确的方法,则为(恰好是decoded_content,而不是content,后者只是它从服务器获得的八位元流)。数据库驱动程序可能不可靠;如果你使用DBD::SQLite和Perl,它会工作,但如果一些其他工具已经将文本存储为某种编码,而不是UTF-8在你的数据库中…嗯…它不会被正确处理直到你写了正确处理它的代码。

输出数据通常更容易,但如果您看到“打印宽字符”,那么您就知道您在某个地方搞砸了编码。该警告意味着“嘿,您正在试图向外部世界泄漏Perl字符,这没有任何意义”。您的程序似乎可以工作(因为另一端通常正确地处理原始Perl字符),但它非常坏,随时可能停止工作。使用显式的Encode::encode!

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

第三个问题是Perl如何处理The Past。很久以前,没有Unicode这样的东西,Perl假设所有东西都是Latin-1文本或二进制。因此,当数据进入程序并开始将其作为文本处理时,Perl将每个八位元视为Latin-1字符。这就是为什么当我们要求“文字化け”的长度时,我们得到了12。Perl假设我们操作的是Latin-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数据,每行末尾有一个poo。太好了,我的程序成功了!

但不是,你只是在做二进制连接。您正在从文件中读取字节,使用chomp删除\n,然后在PILE OF POO字符的UTF-8表示中添加字节。当您修改程序以解码来自文件的数据并对输出进行编码时,您将注意到您得到的是garbage ("ð©")而不是poo。这将使您相信解码输入文件是错误的做法。它不是。

问题是“便便”被含蓄地升级为latin-1。如果你use utf8使文字而不是二进制,那么它将再次工作!

(这是我在帮助人们使用Unicode时遇到的头号问题。他们做了正确的部分,这破坏了他们的计划。这就是未定义结果的悲哀之处:你可以有一个工作了很长时间的程序,但当你开始修复它时,它就坏了。别担心;如果您正在向程序中添加encode/decode语句,而程序中断了,这只是意味着您有更多的工作要做。下次,当你从一开始就考虑Unicode时,就会容易得多!)

以上就是关于Perl和Unicode的全部知识。如果您告诉Perl您的数据是什么,那么在所有流行的编程语言中,它具有最好的Unicode支持。但是,如果您假定它会神奇地知道您提供给它的文本类型,那么您将不可逆转地丢弃数据。仅仅因为您的程序今天可以在UTF-8终端上工作,并不意味着它明天就可以在UTF-16编码的文件上工作。所以,现在就确保它的安全,并为自己省去销毁用户数据的麻烦!

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

您应该启用unicode字符串特性,如果使用v5.14,这是默认值;

你真的不应该使用unicode标识符,特别是对于通过utf8的外部代码,因为它们在perl5中是不安全的,只有cperl可以做到这一点。参见http://perl11.org/blog/unicode-identifiers.html

关于文件句柄/流的utf8:您需要自行决定外部数据的编码。库不可能知道这一点,因为甚至libc也不支持utf8,所以正确的utf8数据很少。有更多的wtf8,窗口畸变的utf8左右。

顺便说一句:Moose并不是真正的“现代珀尔”,他们只是劫持了这个名字。Moose是完美的Larry wall风格的后现代perl,混合了Bjarne stroustrup风格的一切,有一个折衷的perl6语法,例如使用字符串作为变量名,可怕的字段语法,以及一个非常不成熟的幼稚的实现,比正确的实现慢10倍。 Cperl和perl6是真正的现代perl,形式遵循功能,实现得到了简化和优化