如何使用菱形运算符 (<>) 读取 UTF-8?

Posted

技术标签:

【中文标题】如何使用菱形运算符 (<>) 读取 UTF-8?【英文标题】:How do I read UTF-8 with diamond operator (<>)? 【发布时间】:2010-10-05 21:08:04 【问题描述】:

我想在 Perl 中读取 UTF-8 输入,无论它来自标准输入还是来自文件,使用菱形运算符:while(&lt;&gt;)...

所以我的脚本应该可以通过这两种方式调用,像往常一样,提供相同的输出:

./script.pl utf8.txt
cat utf8.txt | ./script.pl

但输出不同!只有第二个调用(使用 cat)似乎按设计工作,正确读取 UTF-8。这是脚本:

#!/usr/bin/perl -w

binmode STDIN, ':utf8';
binmode STDOUT, ':utf8';

while(<>)
    my @chars = split //, $_;
    print "$_\n" foreach(@chars);

如何让它在这两种情况下都正确读取 UTF-8?如果可能,我想继续使用菱形运算符&lt;&gt; 进行阅读。

编辑:

我意识到我可能应该描述不同的输出。我的输入文件包含这个序列:a\xCA\xA7bcat 的方法正确输出:

a
\xCA\xA7
b

但是另一种方法给了我这个:

a
\xC3\x8A
\xC2\xA7
b

【问题讨论】:

【参考方案1】:

尝试使用 pragma open 代替:

use strict;
use warnings;
use open qw(:std :utf8);

while(<>)
    my @chars = split //, $_;
    print "$_" foreach(@chars);

您需要这样做,因为 运算符很神奇。如您所知,它将从 STDIN 或 @ARGV 中的文件中读取。从 STDIN 读取没有问题,因为 STDIN 已经打开,因此 binmode 可以很好地工作。问题是从@ARGV 中的文件读取时,当您的脚本启动并调用 binmode 时,文件未打开。这会导致 STDIN 设置为 UTF-8,但当 @ARGV 有文件时,不使用此 IO 通道。在这种情况下, 运算符为@ARGV 中的每个文件打开一个新的文件句柄。每个文件句柄都会被重置并失去它的 UTF-8 属性。通过使用 pragma open,您可以强制每个新的 STDIN 使用 UTF-8。

【讨论】:

【参考方案2】:

如果你这样做,你的脚本就可以工作:

#!/usr/bin/perl -w

binmode STDOUT, ':utf8';

while(<>)
    binmode ARGV, ':utf8';

    my @chars = split //, $_;
    print "$_\n" foreach(@chars);

读取的魔法文件句柄称为*ARGV,它是 调用 readline 时打开。

但实际上,我非常喜欢明确使用 Encode::decodeEncode::encode 适当的时候。

【讨论】:

您是否必须同时使用 binmode,因为 ARGV 已为多个文件重置? 我看着这个并想,“那行不通!在第一行已经从&lt;&gt; 读取之后,您正在设置binmode”。然而,我试过了,它确实工作。非常神奇。【参考方案3】:

您可以使用 -C 标志默认打开 UTF8:

perl -CSD -ne 'print join("\n",split //);' utf8.txt

开关-CSD无条件开启UTF8;如果您只使用-C,它只会在相关环境变量(LC_ALLLC_TYPELANG)指示时打开 UTF8。详情请见perlrun。

如果您不直接调用 perl,则不建议这样做(特别是,如果您将选项从 shebang 行传递给 perl,它可能无法可靠地工作)。在这种情况下,请参阅其他答案。

【讨论】:

自 perl 5.10 fi.muni.cz/~kas/blog/index.cgi/computers/…987654322@ 以来 -C 开关存在问题 离题:不推荐使用 '#!/usr/bin/perl' 的 shebang 行,详见 perlrun。如果您不使用 perlrun 方法,请使用 #!/usr/bin/env perl,它比 #!/usr/bin/perl 更便携 谢谢,我明确表示你应该只在直接调用 perl 时使用它。 @Hynek-Pichi-Vychodil:十年后的问候! #!/usr/bin/env 技巧有优点也有缺点。现在您通常可以假设perl 安装在/usr/bin 中。有关详细信息,请参阅 Unix & Linux 上的 my answer 至 this question。 @Hynek-Pichi-Vychodil:我尝试将 -CS 放在“#!”上行(Perl 版本 5.32),它似乎又可以工作了。【参考方案4】:

如果您在 while 循环内调用 binmode,那么它会在读入第一行后将句柄切换到 utf8 模式。这可能不是您想要做的。

以下方法可能会更好:

#!/usr/bin/env perl -w
binmode STDOUT, ':utf8';
eof() ? exit : binmode ARGV, ':utf8';
while( <> ) 
    my @chars = split //, $_;
    print "$_\n" foreach(@chars);
 continue 
    binmode ARGV, ':utf8' if eof && !eof();

使用括号调用 eof() 非常神奇,因为它检查 使用的伪文件句柄上的文件结尾。如有必要,它将打开下一个需要读取的句柄,这通常具有使 *ARGV 有效的效果,但不会从中读取任何内容。这允许我们在读取任何内容之前对读取的第一个文件进行 binmode。

稍后,使用 eof(不带括号);这将检查从文件末尾读取的最后一个句柄。在我们从命令行处理每个文件的最后一行之后(或者当 stdin 到达它的末尾时),这将是真的。

显然,如果我们刚刚处理了一个文件的最后一行,调用 eof()(带括号)打开下一个文件(如果有的话),使 *ARGV 有效(如果可以的话),并测试下一个文件的文件结尾。如果下一个文件存在,并且不在文件末尾,那么我们可以安全地在 ARGV 上使用 binmode。

【讨论】:

以上是关于如何使用菱形运算符 (<>) 读取 UTF-8?的主要内容,如果未能解决你的问题,请参考以下文章

不支持菱形运算符[重复]

为啥从 <T> 到 <U> 的隐式转换运算符接受 <T?>?

Maven 编译错误:(使用 -source 7 或更高版本来启用菱形运算符)

CSS3实现的菱形效果代码实例

Java中的菱形运算符是啥? [复制]

在 PHP 中读取 CSV 文件返回黑色菱形问号