ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我知道核心 Perl 脚本存在许多遗留问题,可能会破坏一些东西。但是,在我看来,在 21st 世纪,大型新项目(或具有远大视野的项目)应该从头开始让他们的软件 UTF-8 证明。我仍然没有看到它发生。例如,Moose 启用严格和警告,但不启用 UnicodeModern::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 支持的方式和位置。这太费时了,我发现走老路更容易。现在我看到这里有一个赏金来处理与 Mason 2 相同的问题:How to make Mason2 UTF-8 clean?。因此,它是一个相当新的框架,但将其与 UTF-8 一起使用需要深入了解其内部结构。它就像一个大红色标志:停止,不要使用我!

我真的很喜欢 Perl。但是处理Unicode是痛苦的。我仍然发现自己在靠墙奔跑。某种方式 tchrist 是对的,它回答了我的问题:新项目不会吸引 UTF-8,因为它在 Perl 5 中太复杂了。

很抱歉,但我同意@tchrist 的观点——UTF-8 非常难。没有框架或工具可以“翻转开关”然后正确处理它。这是您在设计应用程序时必须直接考虑的事情——不是任何类型的框架或语言都可以为您处理的事情。如果 rakudo 恰好为您工作,那么您的测试用例不够冒险——因为它将采用 @tchrist 的回答和屠夫中的几个示例。
你究竟希望 Moose 或 Modern::Perl 能做什么?神奇地将文件和数据库中随机编码的字符数据再次变成有效数据?
这意味着什么? Moose 与文本操作无关。为什么它应该知道字符编码,更不用说为你选择一个默认的? (无论如何,您列出的 pragma 不涉及编码的原因是因为约定是 Perl pragma 会影响词法行为。假设整个世界,包括其他模块,都是 UTF-8 只是做错事. 这里不是 PHP 或 Ruby。)
(另外......“大多数现代 Perl 应用程序”在 UTF-8 上中断?我当然从来没有写过一个应用程序,Perl 或其他,这不是 Unicode-clean。)
NB。 tchrist (Tom Christiansen) 发布了关于 Unicode 的 [training.perl.com/OSCON2011/index.htmlTom Christiansen 的 OSCON 2011 材料]。标题为“Unicode 支持大战:好的、坏的和(大部分)丑陋的”的文章谈到了不同编程语言中的 Unicode 支持。只有 Google Go 和 Perl5 支持完整的 Unicode,只有 Google Go 内置(没有提到 Perl6)。

E
Ether

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

将您的 PERL_UNICODE 变量设置为 AS。这使得所有 Perl 脚本都将 @ARGV 解码为 UTF-8 字符串,并将 stdin、stdout 和 stderr 的所有三个编码设置为 UTF-8。这两个都是全局效应,而不是词汇效应。在源文件(程序、模块、库、dohickey)的顶部,通过以下方式突出声明您正在运行 perl 版本 5.12 或更高版本:use v5.12; # 最小的 unicode 字符串功能使用 v5.14; # 最适合 unicode 字符串功能启用警告,因为之前的声明仅启用限制和功能,而不是警告。我还建议将 Unicode 警告提升为异常,因此请同时使用这两行,而不仅仅是其中之一。但是请注意,在 v5.14 下,utf8 警告类包含三个可以单独启用的其他子警告:nonchar、surrogate 和 non_unicode。这些您可能希望对其施加更大的控制。使用警告;使用警告 qw( FATAL utf8 );声明这个源单元被编码为 UTF-8。尽管曾几何时这个 pragma 做了其他事情,但现在它只服务于一个单一的目的,没有其他用途:使用 utf8;声明任何在这个词法范围内但不在其他地方打开文件句柄的东西都是假设该流是用 UTF-8 编码的,除非你另有说明。这样您就不会影响其他模块或其他程序的代码。使用 open qw( :encoding(UTF-8) :std );通过 \N{CHARNAME} 启用命名字符。使用字符名 qw( :full :short );如果您有 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”所必需的:

所有 🐪 源代码默认为 UTF-8。您可以使用 utf8 或 export PERL5OPTS=-Mutf8 来获得它。 🐪 DATA 句柄应该是 UTF-8。您必须在每个包的基础上执行此操作,如 binmode(DATA, ":encoding(UTF-8)")。 🐪 脚本的程序参数默认应该理解为 UTF-8。导出 PERL_UNICODE=A,或 perl -CA,或导出 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 适合所有人。覆盖这两个基础以及您使用 export PERL5OPTS=-Mopen=:utf8,:std 打开的所有流。见单引号。您不想错过 UTF-8 编码错误。尝试导出 PERL5OPTS=-Mwarnings=FATAL,utf8。并确保您的输入流始终二进制模式为 :encoding(UTF-8),而不仅仅是 :utf8。 🐪 应该将 128–255 之间的代码点理解为对应的 Unicode 代码点,而不仅仅是无属性的二进制值。使用功能“unicode_strings”或导出 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 模块访问各种类型的分解函数。 export PERL5OPTS=-MUnicode::Normalize=NFD,NFKD,NFC,NFKD,然后总是通过 NFD 运行传入的内容和来自 NFC 的传出内容。我知道这些还没有 I/O 层,但请参阅 nfc、nfd、nfkd 和 nfkc。 🐪 中使用 eq、ne、lc、cmp、sort、&c&cc 的字符串比较总是错误的。所以你需要@a = Unicode::Collate->new->sort(@b) 而不是@a = sort @b。不妨将其添加到您的导出 PERL5OPTS=-MUnicode::Collate。您可以缓存密钥以进行二进制比较。 🐪 像 printf 和 write 这样的内置函数对 Unicode 数据做错了。前者需要使用 Unicode::GCString 模块,后者需要使用 Unicode::LineBreak 模块。请参阅 uwc 和 unifmt。如果您希望它们计为整数,那么您将不得不通过 Unicode::UCD::num 函数运行您的 \d+ 捕获,因为🐪 的内置 atoi(3) 目前还不够聪明。您将在 👽 文件系统上遇到文件系统问题。一些文件系统默默地强制转换为 NFC;其他人默默地强制转换为 NFD。而其他人仍在做其他事情。有些人甚至完全忽略了这件事,这导致了更大的问题。因此,您必须自己处理 NFC/NFD 以保持理智。所有涉及 az 或 AZ 的 🐪 代码都必须更改,包括 m//、s/// 和 tr///。它应该作为一个尖叫的危险信号脱颖而出,表明您的代码已损坏。但目前尚不清楚它必须如何改变。获得正确的属性并了解它们的外壳,比您想象的要难。我每天都使用 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+/ 的 🐪 变量,那么你就有问题了。您需要查找 /[\$\@\%]\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 模块,如 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 来测试其中的一些内容。考虑如何匹配字符串“niño”中的模式 CVCV(辅音、元音、辅音、元音)。它的 NFD 形式——你最好记得把它放进去——变成“nin\x{303}o”。现在你要做什么?即使假装元音是 [aeiou] (顺便说一句,这是错误的),您也无法执行 (?=[aeiou])\X) 之类的操作,因为即使在 NFD 中,代码点也像 ' ø' 不会分解!但是,使用我刚刚向您展示的 UCA 比较,它将测试等于“o”。你不能依赖 NFD,你必须依赖 UCA。

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

这还不是全部。人们对 Unicode 有一百万个被打破的假设。在他们理解这些事情之前,他们的🐪代码将被破坏。

假定它可以在不指定编码的情况下打开文本文件的代码被破坏。假定默认编码是某种本机平台编码的代码被破坏了。假设日文或中文网页在 UTF-16 中占用的空间比在 UTF-8 中少的代码是错误的。假设 Perl 在内部使用 UTF-8 的代码是错误的。假设编码错误总是会引发异常的代码是错误的。假设 Perl 代码点限制为 0x10_FFFF 的代码是错误的。假设您可以将 $/ 设置为可以使用任何有效行分隔符的代码是错误的。在 casefolding 上假设往返相等的代码,如 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 编码为 "\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 的代码不仅是错误的,而且绝不应允许其肇事者再次从事编程工作。时期。我什至不肯定他们甚至应该被允许再次看到,因为到目前为止这显然对他们没有多大好处。认为有某种方法可以假装文本文件编码不存在的代码是损坏且危险的。还不如把另一只眼睛也伸出来。将未知字符转换为 ?是坏的,愚蠢的,脑死的,并且违反了标准建议,即不要那样做! RTFM 为什么不呢。认为它可以可靠地猜测未标记文本文件的编码的代码是一种致命的傲慢和天真混合,只有宙斯的闪电才能解决。认为您可以使用 🐪 printf 宽度来填充和证明 Unicode 数据的代码是错误的。相信一旦您成功创建了一个给定名称的文件,当您在其封闭目录上运行 ls 或 readdir 时,您实际上会发现使用您创建它的名称的文件是错误的、损坏的和错误的代码。不要对此感到惊讶!认为 UTF-16 是固定宽度编码的代码是愚蠢的、损坏的和错误的。吊销他们的编程许可证。处理来自一个平面的代码点与来自任何其他平面的代码点完全不同的代码实际上是错误的。回学校去。认为 /s/i 之类的东西只能匹配“S”或“s”的代码是错误的。你会感到惊讶。使用 \PM\pM* 来查找字素簇而不是使用 \X 的代码已损坏且错误。应该全心全意地鼓励想要回到 ASCII 世界的人们这样做,并且为了纪念他们的光荣升级,应该免费为他们提供一台预电动手动打字机,以满足他们所有的数据输入需求。发送给他们的消息应通过 ᴀʟʟᴄᴀᴘs 电报发送,每行 40 个字符,并由快递员亲自递送。停止。

😱 𝕾 𝖀 𝕸 𝕸 𝕬 𝕽 𝖄 😱

我不知道你能得到比我写的更多的“🐪 中的默认 Unicode”。嗯,是的,我愿意:您也应该使用 Unicode::CollateUnicode::LineBreak。而且可能更多。

如您所见,您确实需要担心太多的 Unicode 问题,以至于永远不会存在“默认为 Unicode”之类的东西。

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

即使你这样做了,仍然存在需要大量思考才能正确解决的关键问题。没有可以翻转的开关。只有大脑,我的意思是真正的大脑,在这里就足够了。有很多东西你必须学习。以手动打字机为模,你根本不能希望在无知中偷偷溜走。这是 21ˢᵗ 世纪,你不能因为故意无知而希望 Unicode 消失。

你必须学习它。时期。 “一切正常”永远不会那么容易,因为这将保证很多事情都行不通——这使得永远有办法“让一切都正常”的假设无效。

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

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

如果我对perl有一件事,那就是它的Unicode位做和不做的事情,我向您保证了这件事:“ ̲ᴛ̲ʜ̲ᴇ̲ʀ̲ᴇ̲ ̲ᴛ̲ʜ̲ᴇ̲ʀ̲ᴇ̲S̲S̲S̲S̲S̲S̲S̲S̲S̲S̲S̲s̲

你不能仅仅改变一些默认值就可以一帆风顺。确实,我在 PERL_UNICODE 设置为 "SA" 的情况下运行 🐪,但仅此而已,甚至主要用于命令行的东西。对于实际工作,我会经历上面列出的所有步骤,并且非常、**非常**小心。

😈 ¡ƨdləɥ ƨᴉɥʇ ədoɥ puɐ ʻλɐp əɔᴉu ɐ əʌɐɥ ʻʞɔnl poo⅁ 😈


就像 Sherm Pendley 指出的那样:“全部!”。如果我今天写一些新的东西,UTF-8 应该是完成工作的最简单方法。它不是。你的样板证明了这一点。不是每个人都有这样的知识来将这么多不倒翁转向正确的位置。对不起,我度过了漫长而艰难的一天,所以我将在明天的主要条目中评论更多示例。
阅读上面的列表,一个结论应该是显而易见的:不要折叠。只是不要。曾经。计算成本很高,并且语义严重依赖于“语言环境”尝试识别失败的任何内容。
我是唯一一个觉得讽刺的是,tchrist 的这篇文章在 FF/Chrome/IE/Opera 上呈现出如此巨大的不同,有时甚至难以辨认?
虽然我通常喜欢这篇文章,并且确实投了赞成票,但有一件事让我大吃一惊。有很多“代码……被破坏了”。虽然我不反对这种说法,但我认为表现出破碎是件好事。通过这种方式,它会从咆哮到教育(这部分答案)。
@xenoterracide 不,我没有故意使用有问题的代码点;这是一个让您安装涵盖 Unicode 6.0 的 George Douros’s super-awesome Symbola font 的情节。 😈 @depesz 这里没有空间来解释为什么每个破碎的假设都是错误的。 @leonbloy 很多 这通常适用于 Unicode,而不仅仅是 Perl。其中一些材料可能会在 10 月份发布的 🐪 Programming Perl 🐪, 4th edition 中出现。 🎃 我还有 1 个月的时间来 ✍ 工作,那里有 Unicode 是 ᴍᴇɢᴀ;正则表达式也是
P
Palec

处理 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 的字符(即您的程序所操作的字符)转换为可供外部世界使用的八位字节。如果我们可以通过 Internet 或我们的终端发送字符会容易得多,但我们不能:仅八位字节。所以我们必须将字符转换为八位字节,否则结果是不确定的。

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

现在我们将讨论三个问题,这使得这有点具有挑战性。首先是图书馆。他们是否正确处理文本?答案是……他们尝试。如果您下载网页,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 数据。完美,我的程序有效!

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

问题是便便被隐式升级为 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 在他的回答中建议的那样。
P
Peter Mortensen

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

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

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

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

`


我在引用的 utf8::all 模块中看到了很多改进空间。它是在 unicode_strings 功能之前编写的,其中 Fɪɴᴀʟʟʏ ᴀɴᴅ ᴀᴛ Lᴏɴɢ Lᴀsᴛ 修复正则表达式以在其上添加 /u。我不相信它会引发编码错误的异常,这是您真正必须拥有的。它不会加载到尚未自动加载的 use charnames ":full" pragma 中。它不会对 [a-z]printf 字符串宽度发出警告,使用 \n 而不是 \R. 而不是 \X,但也许这些更像是 Perl::Critic 问题。如果是我,我会添加 𝐍𝐅𝐃 和 𝐍𝐅𝐂。
@tchrist utf8::all 的问题跟踪器在这里。 github.com/doherty/utf8-all/issues 他们很想听听您的建议。
@Schwern:ᴇɴᴏᴛᴜɪᴛs,但请随意窃取和捏造我在这里写的东西。老实说,我仍然在感觉/学习可以做什么与应该做什么,以及在哪里做。这是卸载排序的一个很好的示例: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' 这样的小预处理步骤也可以非常好,我不想为他们做出其他人的决定。我还是building my Unicode toolbox
b
brian d foy

我认为您误解了 Unicode 及其与 Perl 的关系。无论您以哪种方式存储数据、Unicode、ISO-8859-1 或许多其他内容,您的程序都必须知道如何解释它作为输入获得的字节(解码)以及如何表示它想要输出的信息(编码)。把那个解释弄错了,你就会弄乱数据。程序内部没有一些神奇的默认设置可以告诉程序外部的东西如何行动。

您认为这很困难,很可能是因为您已经习惯了 ASCII 格式。您应该考虑的一切都被编程语言及其必须与之交互的所有事物简单地忽略了。如果一切都只使用 UTF-8 并且您别无选择,那么 UTF-8 将同样简单。但并非所有东西都使用 UTF-8。例如,您不希望您的输入句柄认为它正在获取 UTF-8 八位字节,除非它实际上是,并且您不希望您的输出句柄是 UTF-8,如果从它们读取的内容无法处理 UTF -8。 Perl 无法知道这些事情。这就是为什么你是程序员。

我不认为 Perl 5 中的 Unicode 太复杂。我认为这很可怕,人们会避开它。有区别。为此,我将 Unicode 放在 Learning Perl, 6th Edition 中,并且在 Effective Perl Programming 中有很多 Unicode 内容。您必须花时间学习和理解 Unicode 及其工作原理。否则你将无法有效地使用它。


我认为你有一个观点:这很可怕。应该是吗?对我来说是 Unicode 的祝福,在 Perl5 中使用它不是(我不认为任何东西都是 ASCII,我的母语至少需要 iso8859-4)。我安装了 Rakudo,我用 UTF-8(在这个有限的沙盒中)尝试的所有东西都是开箱即用的。我错过了什么?我再次强调:有微调的 Unicode 支持很好,但大多数时候不需要。为了消除对话题的恐惧,一种方法是每个人都大量阅读以了解内部情况。其他:我们有特殊的 pragma,所以 use utf8_everywhere 让人高兴。为什么不是最后一个?
我仍然认为你没有抓住重点。什么有效?你不需要了解内部结构。您需要了解外部以及如何处理具有不同编码和相同字符的不同表示的字符串。再次阅读汤姆的建议。他说的大部分内容我打赌你会发现 Rakudo 不适合你。
@wk:再次阅读兰迪的答案。他已经告诉你限制是什么。
@brian d foy:我认为这些限制很好,就像 tchrist 说的那样,每个方面都没有灵丹妙药(我承认:在这里问这个问题之前我没有看到其中的大多数)。所以,当我们用 utf8::all 之类的东西来覆盖很多基本的东西时,没有必要让每个人都构建自己的巨大样板,只是为了让 utf8 处理的基础知识发挥作用。我的意思是“一点也不害怕”:每个人都可以在知道基础知识的情况下开始他的项目。是的,你是对的,还有很多问题。但是当开始变得更容易时,我们将有更多的人参与解决这些问题。恕我直言
@wk - “utf8:all”或“uni::perl”的唯一“错误”只有一个 - 它们不在 CORE 中 - 所以每个人都必须从 CPAN 安装它。如果你认为这不是一个大问题交易 - 请重新考虑 - 是的,使用 utf8 和辅助模块更容易。没有它,CORE perl 仍然支持 unicode - 但非常复杂。这是错误的。
P
Peter Mortensen

在阅读此主题时,我经常觉得人们将“UTF-8”用作“Unicode”的同义词。请区分 Unicode 的“代码点”,它是 ASCII 代码的放大亲戚和 Unicode 的各种“编码”。其中有一些,其中 UTF-8、UTF-16UTF-32 是当前的,还有一些已经过时了。

请注意,UTF-8(以及所有其他编码)存在并且仅在输入或输出中有意义。在内部,从 Perl 5.8.1 开始,所有字符串都保存为 Unicode“代码点”。诚然,您必须启用之前令人钦佩的一些功能。


我同意人们经常将 Uɴɪᴄᴏᴅᴇ 与 UTF-8⧸16⧸32 混淆,但 从根本上和批判性地 不正确 Uɴɪᴄᴏᴅᴇ 只是相对于 ᴀsᴄɪɪ 的一些扩大的字符集。大多数是nothing more than mere ɪsᴏ‑10646Uɴɪᴄᴏᴅᴇ 包含更多内容排序规则、大小写折叠、规范化形式、字素簇、单词和换行符、脚本、数字等价物、宽度、双向性、字形变体、上下文行为、语言环境、正则表达式、组合类、100 个属性,&更多‼
@tchrist:第一步是将数据输入您的程序并输出到外部世界,而不会将其丢弃。然后您可以担心整理、大小写折叠、字形变体等小步骤。
我同意,让 perl 不丢弃输入或输出必须是第一要务。我想要的是有一个可以体现以下虚构对话的模块或编译指示:“-亲爱的 Perl。对于这个程序,所有输入和输出都将是 UTF-8 专有的。请不要破坏我的数据吗?-所以只有你说的 UFT-8。你确定吗?-是的。-真的,真的确定吗?-当然。-你接受如果我收到非 UTF-8 数据,我的行为可能会很奇怪?-是的,很好。 - 那好吧。”
g
geekosaur

野外存在着数量惊人的古老代码,其中大部分以通用 CPAN 模块的形式出现。我发现如果我使用可能受它影响的外部模块,我必须非常小心地启用 Unicode,并且我仍在尝试在我经常使用的几个 Perl 脚本中识别和修复一些与 Unicode 相关的故障(特别是,{1由于转码问题,任何不是 7 位 ASCII 的东西都严重失败)。


我的意思是使用 -C 选项来确保 Perl 与我在 Unicode 方面位于同一页面上,因为我一直让它决定使用 ISO 8859/1 而不是 Unicode,即使我明确设置 $LANG 和 { 3}正确。 (这实际上可能反映了平台语言环境库中的错误。)不管它是什么,我不能在带有重音符号的程序上使用 iTivo 非常烦人,因为执行这项工作的 Perl 脚本会因转换错误而崩溃。
一个没有选项的单独 -C容易出错且容易出错的。你打破了世界。将 PERL5OPT 变量设置为 -C,您就会明白我的意思。我们在 v5.8 中尝试过这种方式,但这是一场灾难。你根本不能也不能告诉那些不期待它的程序现在他们正在处理 Unicode,不管他们喜欢与否。还有安全问题。至少,如果传递二进制数据,执行 print while <> 的任何操作都会中断。所有数据库代码也是如此。这是一个可怕的想法。
实际上,我说的是笼统地说,不是没有选项的-C。我一直在使用的特定调用是 -CSDA。也就是说,我被 5.8.x 困住了很长时间(你好 MacPorts...),所以也许这 的一部分。
我将 PERL_UNICODE 设置为 SA 运行。您不能将其设置为 D。
@tchrist:一些 Perl varmint 是 posting code showing the -CSDA and PERL_UNICODE=SDA usage。请利用您在社区中的影响力。必须阻止他!
r
rurban

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

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

关于文件句柄/流的 utf8:您需要自己决定外部数据的编码。图书馆无法知道这一点,而且由于甚至 libc 都不支持 utf8,因此很少有正确的 utf8 数据。还有更多的wtf8,utf8的windows畸变。

顺便说一句:Moose 并不是真正的“现代 Perl”,他们只是劫持了这个名字。 Moose 是完美的 Larry Wall 风格的后现代 perl 混合了 Bjarne Stroustrup 风格的一切,具有适当的 perl6 语法的折衷偏差,例如使用字符串作为变量名,可怕的字段语法,以及一个非常不成熟的幼稚实现,比一个慢 10 倍正确实施。 cperl 和 perl6 是真正的现代 perl,形式遵循功能,并且实现减少和优化。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅