使用Perl循环遍历文件中的行的最具防御性的方法是什么?

Can*_*ice 14 perl defensive-programming while-loop

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

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

然而,在回答另一个问题时,Evan Carroll编辑了我的答案,将我的while陈述改为:

while ( defined( my $line = <$fh> ) ) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

他的理由是,如果你有一条线0(它必须是最后一条线,否则它将有一个回车)然后while如果你使用我的声明你将会过早退出($line将被设置为"0",并且返回值来自因此,赋值也将"0"被评估为错误).如果检查已定义,则不会遇到此问题.这很有道理.

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

然后我想,"啊哈,也许这个价值实际上并不0存在,也许那里还有别的事情搞砸了!" 所以我使用Dump()Devel::Peek,这就是它给了我的东西:

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

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

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

Rob*_*t P 18

因为

 while (my $line = <$fh>) { ... }
Run Code Online (Sandbox Code Playgroud)

实际上编译为

 while (defined( my $line = <$fh> ) ) { ... }
Run Code Online (Sandbox Code Playgroud)

它可能在非常旧版本的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
Run Code Online (Sandbox Code Playgroud)

所以你已经很好了!

  • @j_random_hacker,因为perl5没有正式的语言规范,解释器的行为是肯定的答案 (4认同)
  • 不是-1,因为我同意`B :: Deparse'对于探索Perl在你难倒时正在做的事情是有用的,但恕我直言使用它来"回答"这样的问题不是......对.它只告诉你Perl将在你尝试它的少数特定情况下做什么,它没有告诉你任何关于这种行为将发生的一般条件.只有语言规范可以告诉你. (2认同)
  • @j_random_hacker:在perl的辩护中,perl6结束了这一点.有一个规范,有几个独立的实现可供比较.规范是最后一个词 (2认同)

Eth*_*her 13

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

在标量上下文中,评估尖括号中的文件句柄会产生该文件的下一行(包括换行符,如果有的话),或者在文件结尾或出错时的"undef".当$ /设置为"undef"(有时称为文件 - 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>;
Run Code Online (Sandbox Code Playgroud)

这也行为相似,但避免$ _:

while (my $line = <STDIN>) { print $line }
Run Code Online (Sandbox Code Playgroud)

在这些循环结构中,然后测试指定的值(无论是自动还是显式赋值)以查看它是否已定义.定义的测试避免了line具有字符串值的问题,该字符串值将被Perl视为false,例如""或"0"没有尾随换行符.如果你真的想要这些值来终止循环,那么它们应该被明确地测试:

while (($_ = <STDIN>) ne '0') { ... }
while (<STDIN>) { last unless $_; ... }
Run Code Online (Sandbox Code Playgroud)

在其他布尔上下文中,如果"use warnings"pragma或-w命令行开关($ ^ W变量)生效,则没有显式"已定义"测试或比较的"<filehandle>"会引发警告.