15

我通常使用以下代码遍历文件中的行:

open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
  ...
}

然而,在回答另一个问题时,埃文卡罗尔编辑了我的答案,将我的while陈述改为:

while ( defined( my $line = <$fh> ) ) {
  ...
}

他的理由是,如果你有一行是0(它必须是最后一行,否则它会有一个回车),那么while如果你使用我的语句,你会过早退出 ($line将设置为"0",返回值来自因此,分配也将"0"被评估为假)。如果您检查定义性,那么您就不会遇到这个问题。这很有意义。

所以我试了一下。我创建了一个文本文件,其最后一行0没有回车。我通过我的循环运行它并且循环没有过早退出。

然后我想,“啊哈,也许价值实际上不是0,也许还有其他东西把事情搞砸了!” 所以我使用Dump()了 from Devel::Peek,这就是它给我的:

SV = PV(0x635088) at 0x92f0e8
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0X962600 "0"\0
  CUR = 1
  LEN = 80

这似乎告诉我该值实际上是 string "0",因为如果我调用Dump()我明确设置为的标量,我会得到类似的结果"0"(唯一的区别是 LEN 字段 - 从文件 LEN 是 80,而从标量 LEN 是 8)。

那么有什么关系呢?while()如果我传递一条只有"0"没有回车的行,为什么我的循环不会过早退出?Evan 的循环实际上更具防御性,还是 Perl 在内部做了一些疯狂的事情,这意味着你不需要担心这些事情,while()实际上只有在你命中时才会退出eof

4

3 回答 3

18

因为

 while (my $line = <$fh>) { ... }

实际上编译为

 while (defined( my $line = <$fh> ) ) { ... }

在非常旧的 perl 版本中可能需要它,但现在不再需要了!您可以通过在脚本上运行 B::Deparse 看到这一点:

>perl -MO=Deparse
open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
  ...
}

^D
die "Could not open file $file for reading: $!\n" unless open my $fh, '<', $file;
while (defined(my $line = <$fh>)) {
    do {
        die 'Unimplemented'
    };
}
- syntax OK

所以你已经准备好了!

于 2010-09-22T22:02:11.313 回答
13

顺便说一句,这在perldoc perlop的 I/O Operators 部分中有介绍:

在标量上下文中,评估尖括号中的文件句柄会产生该文件的下一行(包括换行符,如果有的话),或者在文件结尾或错误时产生“undef”。当 $/ 设置为“undef”(有时称为 file-slurp 模式)并且文件为空时,它第一次返回 '',随后返回“undef”。

通常,您必须将返回的值分配给变量,但有一种情况会发生自动分配。当且仅当输入符号是“while”语句的条件内唯一的东西(即使伪装成“for(;;)”循环),该值会自动分配给全局变量 $_,破坏任何以前在那里。(这对您来说可能看起来很奇怪,但您几乎会在您编写的每个 Perl 脚本中使用该结构。) $_ 变量不是隐式本地化的。您必须输入“本地 $_;” 如果您希望发生这种情况,请在循环之前。

以下几行是等效的:

while (defined($_ = <STDIN>)) { print; }
while ($_ = <STDIN>) { print; }
while (<STDIN>) { print; }
for (;<STDIN>;) { print; }
print while defined($_ = <STDIN>);
print while ($_ = <STDIN>);
print while <STDIN>;

这也表现类似,但避免了 $_ :

while (my $line = <STDIN>) { print $line }

在这些循环结构中,分配的值(无论分配是自动的还是显式的)然后被测试以查看它是否被定义。定义的测试避免了 line 的字符串值会被 Perl 视为 false 的问题,例如没有尾随换行符的 "" 或 "0"。如果您真的要让这些值终止循环,则应明确测试它们:

while (($_ = <STDIN>) ne '0') { ... }
while (<STDIN>) { last unless $_; ... }

在其他布尔上下文中,如果“使用警告”杂注或 -w 命令行开关($^W 变量)有效,则没有显式“定义”测试或比较的“<filehandle>”会引发警告。

于 2010-09-22T22:17:59.767 回答
1

虽然编译的形式考虑到有多种情况是正确的,但while (my $line=<$fh>) { ... }如果您没有while (defined( my $line = <$fh> ) ) { ... }显式defined循环或测试<>.

这里有几个例子:

#!/usr/bin/perl
use strict; use warnings;

my $str = join "", map { "$_\n" } -10..10;
$str.="0";
my $sep='=' x 10;
my ($fh, $line);

open $fh, '<', \$str or 
     die "could not open in-memory file: $!";

print "$sep Should print:\n$str\n$sep\n";     

#Failure 1:
print 'while ($line=chomp_ln()) { print "$line\n"; }:',
      "\n";
while ($line=chomp_ln()) { print "$line\n"; } #fails on "0"
rewind();
print "$sep\n";

#Failure 2:
print 'while ($line=trim_ln()) { print "$line\n"; }',"\n";
while ($line=trim_ln()) { print "$line\n"; } #fails on "0"
print "$sep\n";
last_char();

#Failure 3:
# fails on last line of "0" 
print 'if(my $l=<$fh>) { print "$l\n" }', "\n";
if(my $l=<$fh>) { print "$l\n" } 
print "$sep\n";
last_char();

#Failure 4 and no Perl warning:
print 'print "$_\n" if <$fh>;',"\n";
print "$_\n" if <$fh>; #fails to print;
print "$sep\n";
last_char();

#Failure 5
# fails on last line of "0" with no Perl warning
print 'if($line=<$fh>) { print $line; }', "\n";
if($line=<$fh>) { 
    print $line; 
} else {
    print "READ ERROR: That was supposed to be the last line!\n";
}    
print "BUT, line read really was: \"$line\"", "\n\n";

sub chomp_ln {
# if I have "warnings", Perl says:
#    Value of <HANDLE> construct can be "0"; test with defined() 
    if($line=<$fh>) {
        chomp $line ;
        return $line;
    }
    return undef;
}

sub trim_ln {
# if I have "warnings", Perl says:
#    Value of <HANDLE> construct can be "0"; test with defined() 
    if (my $line=<$fh>) {
        $line =~ s/^\s+//;
        $line =~ s/\s+$//;
        return $line;
    }
    return undef;

}

sub rewind {
    seek ($fh, 0, 0) or 
        die "Cannot seek on in-memory file: $!";
}

sub last_char {
    seek($fh, -1, 2) or
       die "Cannot seek on in-memory file: $!";
}

我并不是说这些是 Perl 的好形式!我是说它们是可能的;尤其是失败 3,4 和 5。请注意在数字 4 和 5 上没有 Perl 警告的失败。前两个有自己的问题...

于 2010-09-23T02:17:06.633 回答