即使我没有从文件中读取有问题的数据,Perl 也会警告无效编码
Perl warns about invalid encoding even if I don't read the problematic data from the file
我正在尝试从文件的第一部分读取行,该文件包含以 cp1252 编码编码的文本 header,并在特定关键字后包含二进制数据。
问题
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 的 Hexdump,其中 Wrong 后面的字节 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只是为了表明我没有读太远。这是我读到的最后一行和 "binary" 数据之间的有效行。
这是显示我只读过两行的输出,但 perl 仍然发出警告:
user@host$ perl linebug.pl
cp1252 "\x81" does not map to Unicode at ./linebug.pl line 6.
First
Last
user@host$
可以看出,我的程序读取并打印了前两行,然后退出。它不应该尝试读取和解释任何其他内容,但我仍然收到关于 \x81
未映射到 Unicode 的警告。
问题
- 为什么会发出警告?我没有读线。直觉告诉我它正在尝试提前阅读,但为什么它会尝试解码?
- 是否有解决方法或更好的方法来处理编码从一个部分更改为另一个部分的文件?
我仍然想要读取初始行时的警告,以防文件损坏。
Perl 以 8 KiB 块的形式从文件中读取,因此一次读取的不止一行。数据在读取时立即解码(因为必须解码流以找到行尾),因此会注意到意外编码并发出警告。
处理此问题的一种方法:使用 non-buffered 读取,通过 sysread,一次读取较小的块。
计算读取的字符数,一旦您 运行 进入该位置,您可以备份并一次继续读取字符,再次计算它们,以便检测准确的位置。有关识别发出警告的位置的工作示例,请参阅 this post。
为了能够在那里停下来,您可能希望从 $SIG{__WARN__}
处理程序中抛出 die
,并将所有代码放入 eval
。这将允许您在发出警告的地方停下来并收回控制权。
当您读到那个位置时,您可以 re-open 文件的编码适合文件的其余部分,然后搜索到那个位置并阅读其余部分。
我现在无法编写和测试所有内容,希望这会有所帮助。
文件没有行的概念;它们只是字节流。 Perl 必须从 OS 的文件中请求一定数量的字节,并找出该行结束的位置,以便 return 向程序添加一行。
Perl 可以从 OS 中一次请求一个字节,直到它有一个完整的行,但那将是非常低效的。进行系统调用涉及很多开销。因此,Perl 一次请求 8 KiB。
然后,在 Perl 可以确定行结束位置之前必须对原始数据进行解码,因为原始 0A
不一定表示行结束。
与为什么不一次从文件中读取一个字节类似,要求解码器只解码下一个字符是低效的。每次开始和停止解码时都会涉及开销。因此,Perl 在读取数据时对其读取的所有数据进行解码。
所以这意味着 Perl 对程序的读取和解码都比它 return 多。
解决方案是将文件视为二进制文件(因为如果编码按部分更改,它就不是真正的文本文件)并自己进行解码。
如果您正在处理像 cp1252 这样的 single-byte 编码,您可以继续使用 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/;
}
如果您没有使用 single-byte 编码,您可能必须改用 read
。 (由于 UTF-8 的设计方式,您可以继续对 UTF-8 使用 readline
。)使用 read
时,确切的解决方案取决于一些细节(与确定要读取多少和读取多少有关)解码)。
我正在尝试从文件的第一部分读取行,该文件包含以 cp1252 编码编码的文本 header,并在特定关键字后包含二进制数据。
问题
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 的 Hexdump,其中 Wrong 后面的字节 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只是为了表明我没有读太远。这是我读到的最后一行和 "binary" 数据之间的有效行。
这是显示我只读过两行的输出,但 perl 仍然发出警告:
user@host$ perl linebug.pl
cp1252 "\x81" does not map to Unicode at ./linebug.pl line 6.
First
Last
user@host$
可以看出,我的程序读取并打印了前两行,然后退出。它不应该尝试读取和解释任何其他内容,但我仍然收到关于 \x81
未映射到 Unicode 的警告。
问题
- 为什么会发出警告?我没有读线。直觉告诉我它正在尝试提前阅读,但为什么它会尝试解码?
- 是否有解决方法或更好的方法来处理编码从一个部分更改为另一个部分的文件?
我仍然想要读取初始行时的警告,以防文件损坏。
Perl 以 8 KiB 块的形式从文件中读取,因此一次读取的不止一行。数据在读取时立即解码(因为必须解码流以找到行尾),因此会注意到意外编码并发出警告。
处理此问题的一种方法:使用 non-buffered 读取,通过 sysread,一次读取较小的块。
计算读取的字符数,一旦您 运行 进入该位置,您可以备份并一次继续读取字符,再次计算它们,以便检测准确的位置。有关识别发出警告的位置的工作示例,请参阅 this post。
为了能够在那里停下来,您可能希望从 $SIG{__WARN__}
处理程序中抛出 die
,并将所有代码放入 eval
。这将允许您在发出警告的地方停下来并收回控制权。
当您读到那个位置时,您可以 re-open 文件的编码适合文件的其余部分,然后搜索到那个位置并阅读其余部分。
我现在无法编写和测试所有内容,希望这会有所帮助。
文件没有行的概念;它们只是字节流。 Perl 必须从 OS 的文件中请求一定数量的字节,并找出该行结束的位置,以便 return 向程序添加一行。
Perl 可以从 OS 中一次请求一个字节,直到它有一个完整的行,但那将是非常低效的。进行系统调用涉及很多开销。因此,Perl 一次请求 8 KiB。
然后,在 Perl 可以确定行结束位置之前必须对原始数据进行解码,因为原始 0A
不一定表示行结束。
与为什么不一次从文件中读取一个字节类似,要求解码器只解码下一个字符是低效的。每次开始和停止解码时都会涉及开销。因此,Perl 在读取数据时对其读取的所有数据进行解码。
所以这意味着 Perl 对程序的读取和解码都比它 return 多。
解决方案是将文件视为二进制文件(因为如果编码按部分更改,它就不是真正的文本文件)并自己进行解码。
如果您正在处理像 cp1252 这样的 single-byte 编码,您可以继续使用 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/;
}
如果您没有使用 single-byte 编码,您可能必须改用 read
。 (由于 UTF-8 的设计方式,您可以继续对 UTF-8 使用 readline
。)使用 read
时,确切的解决方案取决于一些细节(与确定要读取多少和读取多少有关)解码)。