当前文件行数为 $. 变量。

13

我知道可以使用内置变量 $. 获取正在循环遍历的文件的当前行号。作为一个实验,我尝试使用该变量将文件中每一行的值前缀为 $. 的值(即当前行号)。然而,这并没有按照预期工作。例如,给定以下文件内容:

line one
line two
line three

我期望以下代码会给每行加上它的行号

for my $line (<FILE>) {
    print "$. : $line";
}

但是,相反,它会给出以下输出

3 line one
3 line two
3 line three

在文件中每行都加上该文件中的行数,而不是当前行数。


参见:https://dev59.com/WG025IYBdhLWcg3wl3Am#5921785 - KJ7LNW
1个回答

21

这是因为您编写的循环方式在循环行之前读取整个文件。除非您有特殊原因需要比简单的顺序访问文件更好的功能,否则应该使用while而不是for,就像这样:

while (my $line = <FILE>) {
  print "$. : $line";
}

当在列表上下文中调用<filehandle>(就像在您的for循环中一样)时,它将文件的全部内容作为行列表返回。 因此,您的代码的行为方式基本上与您编写以下内容相同:

my @lines = <FILE>;            # now $. is set to the end of the file 
for my $line (@lines) { ... }  # you're just looping over an array, not touching $.

为了达到你想要的结果,你应该在标量上下文中反复调用 <>(在 while 条件中进行赋值),以从文件中逐行获取并执行循环体,并将 $. 设置为正确的行数。

此外,全局文件句柄被认为是不良实践。出于多种原因,最好使用由词法变量引用的文件句柄,像这样:

open my $file, '<', $filename or die $!;
while (my $line = <$file>) {
  print "$. : $line";
}

还有,由于$.是一个包含最近执行的读操作的行号的全局变量,如果在<$file>print之间有可能出现另一次读取,请不要依赖它。相反,询问正在使用的文件句柄的行号:

open my $file, '<', $filename or die $!;
while (my $line = <$file>) {
  print $file->input_line_number, " : $line";
}

即使使用全局文件句柄,这也可以正常工作,尽管有些笨拙:

while (my $line = <FILE>) {
  print ${\*FILE}->input_line_number, " : $line";
}

即使是被空的<>读取的默认值,也被真正命名为ARGV

while (my $line = <>) {
  print ${\*ARGV}->input_line_number, " : $line";
}

感谢您对裸文件句柄的澄清和说明 - 我想我会拿一本《现代Perl》来熟悉现代技术。 - Friedrich 'Fred' Clausen
4
好的回答。请注意,在那种情况下,检查 defined 是不必要的-- Perl 会自动处理。我找不到文档中的位置,但这个示例说明了这一点:perl -MO=Deparse -e "while (my $line = <$fh>){print}" - FMc
@FMc 在结尾处,read 返回 undef,在条件语句中被视为 false。请参阅 Perlsyn 中的真和假 - user2676699
2
@user2676699 我认为这不是重点。相反,重点是Perl会为您插入对defined()的调用。请参阅我发布的代码的输出以及此链接:http://perldoc.perl.org/perlop.html#I%2fO-Operators。 - FMc
3
当你使用<$fh>读取文件并print内容时,如果中间的代码也使用<$fh>操作文件,则$.可能会在两个操作之间发生变化。因为$.总是指向最近读取文件句柄的行号,而不是词法上的变量。要避免这种情况,可以使用IO::Handle模块中的$fh->input_line_number方法来获取行号。 - Kent Fredric
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接