这是一个没有特殊字符的简单文本文件,名为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
。
答案 0 :(得分:3)
对于那些寻找 TL; DR 的人, seek
和tell
以字节为单位工作。 seek
如果使用tell
Perl的seek
运营商的文档相当笨拙,但它有这个
寻求FILEHANDLE,POSITION,WHENCE
WHENCE的值为0,将新位置设置为POSITION ...
和
注意 in bytes :即使文件句柄已经设置为对字符进行操作(例如使用
:encoding(utf8)
开放层),tell()也会返回字节偏移,而不是字符偏移(因为实现会使seek()和tell()相当慢)。
虽然这提到了问题但没有明确说明
seek
和tell
在文件中使用并返回字节偏移,而不管其他任何PerlIO图层。这意味着他们使用与sysread
类似的术语,这与Perl的流式IO无关,尽管seek
和tell
尊重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的问题
我希望这能为您解释一些异常情况,但请检查您的代码和结果。