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