如何使用菱形运算符 (<>) 读取 UTF-8?
Posted
技术标签:
【中文标题】如何使用菱形运算符 (<>) 读取 UTF-8?【英文标题】:How do I read UTF-8 with diamond operator (<>)? 【发布时间】:2010-10-05 21:08:04 【问题描述】:我想在 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
【问题讨论】:
【参考方案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::decode
和
Encode::encode
适当的时候。
【讨论】:
您是否必须同时使用 binmode,因为 ARGV 已为多个文件重置? 我看着这个并想,“那行不通!在第一行已经从<>
读取之后,您正在设置binmode
”。然而,我试过了,它确实工作。非常神奇。【参考方案3】:
您可以使用 -C
标志默认打开 UTF8:
perl -CSD -ne 'print join("\n",split //);' utf8.txt
开关-CSD
无条件开启UTF8;如果您只使用-C
,它只会在相关环境变量(LC_ALL
、LC_TYPE
和LANG
)指示时打开 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?>?