即使我没有从文件中读取有问题的数据,Perl也会警告有关无效编码

时间:2019-06-06 23:24:37

标签: perl file-io character-encoding

我正在尝试从文件的第一部分读取行,该文件的第一部分包含以cp1252编码的文本标题,并且在特定关键字之后包含二进制数据。

问题

Perl警告有关我从未读过的文件部分中的无效编码。我在两个文件中创建了一个示例来演示该问题。

linebug.pl 的内容:

#!/usr/bin/perl
use 5.028;
use strict;
use warnings;
open( my $fh, "<:encoding(cp1252)", "testfile" );
while( <$fh> ) {
    print;
    last if /Last/;
}

testfile 的十六进制转储,其中故意添加了文本错误之后的字节0x81,因为它不是有效的cp1252代码点:

46 69 72 73 74 0a         |First.|
4c 61 73 74 0a            |Last.|
42 75 66 66 65 72 0a      |Buffer.|
57 72 6f 6e 67 81 0a      |Wrong..|

第三行 Buffer 只是为了清楚地表明我读得不太远。这是我读取的最后一行和“二进制”数据之间的有效行。

以下输出显示我只读过两行,但是perl仍然发出警告:

user@host$ perl linebug.pl
cp1252 "\x81" does not map to Unicode at ./linebug.pl line 6.
First
Last
user@host$

可以看出,我的程序读取并打印了前两行,然后退出。它永远都不应尝试阅读和解释其他任何内容,但是我仍然收到有关\x81未映射到Unicode的警告。

问题

  • 为什么会警告?我没有读线。直觉告诉我它正在尝试提前阅读,但是为什么会尝试解码?
  • 在编码从一个部分变为另一部分的文件中,是否有解决方法或更好的方式来处理文件?

在读取初始行时,如果文件已损坏,我仍然需要警告。

2 个答案:

答案 0 :(得分:3)

文件没有行的概念;它们只是字节流。 Perl必须从OS的文件中请求一定数量的字节,并弄清楚行的结尾,以便将行返回给程序。

Perl可以一次从OS请求单个字节,直到获得完整的行为止,但这将是非常低效的。进行系统调用涉及很多开销。因此,Perl一次请求8 KiB。

然后,必须对原始数据进行解码,然后Perl才能确定行的结束位置,因为原始0A不一定表示行的结束。

类似于为什么为什么一次不从文件中读取一个字节,要求解码器仅解码下一个字符将是低效的。每次您开始和停止解码都涉及开销。这样,Perl会在读取数据时对其进行解码。

因此,这意味着Perl读取和解码的内容多于返回到程序的内容。


解决方案是将文件视为二进制文件(如果按部分更改编码,因为它实际上不是文本文件),然后自己进行解码。

如果要处理像cp1252这样的单字节编码,则可以继续使用readline(又名<$fh>)。但是,您无需将Perl搜索换行的代码点(0A),而是需要将$/设置为代码点的编码。碰巧的是,cp1252也是0A,因此不需要更改。

use Encode qw( decode );

open( my $fh, "<:raw", $qfn )
   or die( "Can't open \"$qfn\": $!\n" );

while( <$fh> ) {
    $_ = decode( 'cp1252', $_ );      # :encoding(cp1252)
    s/\r\n\z/\n/ if $^O eq 'Win32';   # :crlf
    print;
    last if /Last/;
}

如果您未使用单字节编码,则可能必须切换为使用read。 (由于它的设计方式,您可以继续将readline用于UTF-8。)使用read时,确切的解决方案取决于一些细节(与确定读取多少和多少有关)。进行解码)。

答案 1 :(得分:2)

Perl从文件中读取8个KiB块,因此一次读取的行多。数据在读取时即被解码(因为必须对流进行解码才能找到行尾),因此会注意到并发出意外的编码。

一种解决方法:通过sysread使用非缓冲读取,并一次读取较小的块。

计算阅读的字符数,一旦碰到该位置,您就可以备份并继续一次阅读字符,再次对它们进行计数,以便检测出确切的位置。请参阅this post,以获取识别发出警告的地点的有效示例。

为了能够在此停下来,您可能需要将die$SIG{__WARN__}处理程序中抛出,并将所有代码都包含在eval中。这样一来,您就可以在发出警告的地方停下来进行控制。

阅读完该位置后,您可以按照适合文件其余部分的编码重新打开该文件,然后搜索该位置并读取其余部分。

我现在无法编写和测试所有内容,希望这会有所帮助。