我正在尝试从文件的第一部分读取行,该文件的第一部分包含以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的警告。
在读取初始行时,如果文件已损坏,我仍然需要警告。
答案 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
中。这样一来,您就可以在发出警告的地方停下来进行控制。
阅读完该位置后,您可以按照适合文件其余部分的编码重新打开该文件,然后搜索该位置并读取其余部分。
我现在无法编写和测试所有内容,希望这会有所帮助。