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

时间:2009-02-06 06:28:05

标签: perl unicode utf-8 input

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

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

./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?如果可能的话,我想继续使用钻石操作员<>进行阅读。

编辑:

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

a
\xCA\xA7
b

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

a
\xC3\x8A
\xC2\xA7
b

4 个答案:

答案 0 :(得分:56)

尝试使用pragma open:

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

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

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

答案 1 :(得分:17)

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

#!/usr/bin/perl -w

binmode STDOUT, ':utf8';

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

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

魔法文件句柄&lt;&gt;读取被称为*ARGV,它是 当你打电话给readline时打开。

但实际上,我是明确使用Encode::decode和 适当时Encode::encode

答案 2 :(得分:9)

默认情况下,您可以使用-C标志打开UTF8:

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

交换机-CSD无条件地打开UTF8;如果仅使用-C,则仅当相关环境变量(LC_ALLLC_TYPELANG)表明时,它才会启用UTF8。有关详细信息,请参阅perlrun

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

答案 3 :(得分: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();
}

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

后来,使用了eof(没有parens);这将检查从文件末尾读取的最后一个句柄。在我们从命令行处理每个文件的最后一行之后(或当stdin到达它结束时),它将是真的。

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