48

我想使用菱形运算符读取 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\xA7b. cat正确输出的方法:

a
\xCA\xA7
b

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

a
\xC3\x8A
\xC2\xA7
b
4

4 回答 4

63

尝试使用 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。

于 2009-02-06T06:52:09.027 回答
18

如果您这样做,您的脚本将起作用:

#!/usr/bin/perl -w

binmode STDOUT, ':utf8';

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

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

<> 读取的魔法文件句柄被称为*ARGV,当你调用 readline 时它​​被打开。

但实际上,我喜欢在适当的时候明确使用Encode::decode和 。Encode::encode

于 2009-02-06T08:33:17.873 回答
10

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

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

开关-CSD无条件开启UTF8;如果您简单地使用它,它将仅在相关环境变量(和)指示的情况-C下打开 UTF8 。有关详细信息,请参阅perlrunLC_ALLLC_TYPELANG

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

于 2009-02-06T08:50:27.480 回答
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。

于 2011-02-02T23:44:18.647 回答