为什么以utf 8模式打开文件会改变搜索的行为?

时间:2016-04-26 16:35:02

标签: perl file utf-8 seek

这是一个没有特殊字符的简单文本文件,名为utf-8.txt,内容如下。

foo bar baz
one two three

新行遵循unix约定(一个字节),因此文件的整个大小为26 = 11 + 1 + 13 + 1.(11 = foo bar baz,13 = {{1} }。

如果我使用以下perl脚本读取文件

one two three

打印

use warnings;
use strict;

open (my $f, '<', 'utf8.txt');
<$f>;
seek($f, -4, 1);
my $xyz = <$f>;
print "$xyz<";

这是预期的,因为baz < 命令返回四个字符,新行和三个属于seek

如果我现在将baz语句更改为

open

输出变为

open (my $f, '<:encoding(UTF-8)', 'utf8.txt');

也就是说, baz < 命令返回五个字符(或者它返回四个字符但跳过新行)。

预计会出现这种情况吗?是否有标志或某种东西可以解决这种问题?

修改

根据 Andrzej A. Filip 建议,当我在seek语句之后添加print join("+",PerlIO::get_layers($f)),"\n";时,它会打印在&#34; normal&#34;公开案例:open以及unix+crlf案例:open...encoding

1 个答案:

答案 0 :(得分:3)

对于那些寻找 TL; DR 的人, seektell以字节为单位工作。 seek如果使用tell

返回的值,则应始终可以


Perl的seek运营商的文档相当笨拙,但它有这个

  

寻求FILEHANDLE,POSITION,WHENCE

     

WHENCE的值为0,将新位置设置为POSITION ...

  

注意 in bytes :即使文件句柄已经设置为对字符进行操作(例如使用:encoding(utf8)开放层),tell()也会返回字节偏移,而不是字符偏移(因为实现会使seek()和tell()相当慢)。

虽然这提到了问题但没有明确说明

seektell在文件中使用并返回字节偏移,而不管其他任何PerlIO图层。这意味着他们使用与sysread类似的术语,这与Perl的流式IO无关,尽管seektell尊重Perl的缓冲,而sysread不是

不只是:utf8:encoding层会混淆您可能会遇到的单位:Windows :crlf图层也会产生影响,因为它会在流式传输之前将CR LF对转换为LF输入和输出后。这显然会导致每行文本的差异,但据我所知,这在Perl的文档中没有提到; Linux和OSX几乎是所有其他Perl平台的丑陋姐妹

让我们来看看你的代码。我已经在我的Windows 10和Windows 7系统上运行了这段代码(它与您提出的问题中的代码相同),甚至用Windows 98启动虚拟机来尝试同样的事情

use warnings;
use strict;

open (my $f, '<', 'utf8.txt');
print join("+",PerlIO::get_layers($f)),"\n";
<$f>;
seek($f, -4, 1);
my $xyz = <$f>;
print "$xyz<";

所有这些都输出

unix+crlf
az

这是我的预期,而不是你说的。这是重中之重,因为我们讨论的是单字节偏移

您的文件包含此

foo bar baz\r\none two three

第一次读取从一开始就带我们13个字符。 Perl读取了foo bar baz\r\n并删除了CR,将foo bar baz\n交给它丢弃的程序。细

现在你seek($f, -4, 1)

第三个参数1是SEEK_CUR,这意味着您想要将当前读指针相对于当前位置移动

请不要使用魔术数字。 Perl在这里几乎暴露了潜在的C file library,你需要对它负责。传递1作为第三个参数是神秘且不负责任的。没有人阅读你的代码会知道你写的是什么

这样做

use Fcntl ':seek'

然后您可以编写更多可理解的代码。至少人们可以谷歌SEEK_CUR,而尝试与1相同会比没有结果的更糟糕

seek($f, -4, SEEK_CUR)

因为它让我们其余的人有机会理解你的代码

所以你要寻找13个字节,加上-4就是9.那就在b的{​​{1}}之后,所以得到baz

这就是我在所有这些不同的Windows机器上运行所有代码的过程。我必须认为问题出在你的代码控制而不是Perl,除了CRLF的问题

我希望这能为您解释一些异常情况,但请检查您的代码和结果。