2010-09-22 14 views
14

我通常是通過使用下面的代碼文件中的行循環:使用Perl循環遍歷文件中的行最具防禦性的方法是什麼?

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

然而,in answering another questionEvan Carroll編輯我的回答,改變我的while聲明:

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

他的理由是,如果你有一條線是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 

這似乎在告訴我,值實際上是字符串"0",因爲我得到了類似的結果,如果我叫Dump()上標I」已明確設置爲"0"(唯一的區別在於LEN字段 - 從文件LEN是80,而標量LEN是8)。

那麼有什麼交易?爲什麼我的while()循環沒有提前退出,如果我通過一條只有"0"且沒有回車符的行? Evan的循環實際上更具防禦性,還是Perl在內部做了一些瘋狂的事情,這意味着你不需要擔心這些事情,而while()實際上只有在你點擊eof時纔會退出?

+1

如果你正在尋找防守代碼,使用[坦克]( http://en.wikipedia.org/wiki/Tank)。 – 2010-09-22 22:02:48

+3

這就是爲什麼我不會編輯某人回覆的意思(我只是修復明顯的錯別字)。如果您認爲有遺漏或可以改進,請添加評論。並感謝您調查內部! – Ether 2010-09-22 22:08:56

回答

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 

所以你已經很好走了!

+1

PS,我愛...絕對愛在5.12及以上,'...是否是有效的語法。愛它。 – 2010-09-22 22:02:52

+0

哦,我的。我想知道是否有人被這個隱含的'defined'咬到了屁股裏? – zigdon 2010-09-22 22:04:18

+0

如果有人真的在編寫代碼來檢查從沒有行結束的文件中讀取的un-chomp()ed行是否以這種方式評估爲False,他們就會得到他們應得的。 Perl的DWIM態度通常是正確的。 – geoffspear 2010-09-22 22:07:39

13

順便說一句,這是覆蓋在的perldoc perlop的I/O操作員部分:

在標量環境,在尖括號評估文件句柄產生從該文件中的下一行(換行,如果有的話,包括),或在文件結束或錯誤時顯示「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 } 

在這些循環結構,所分配的值(分配是否是自動的或顯式的)然後進行測試,看它是否被定義。定義的測試避免了行中有一個字符串值被Perl視爲假的問題,例如「」或沒有尾隨換行符的「0」。如果你真的是這樣的值,以終止循環,他們應該明確地進行測試:

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

在其他布爾值的上下文,「<文件句柄>」沒有一個明確的「定義」測試或比較引出如果警告「使用警告」雜注或-w命令行開關($^W變量)有效。

+1

好的答案,所以我刪除了我的。但是,Perl文檔是誤導性的 - 他們說,「如果**並且只有輸入符號是while語句的條件內唯一的東西」 - 但後來與「只有」部分相矛盾,通過顯示'while(my $ line = )'也表現相同的方式。讓我們想知道這個DWIMmery會在什麼情況下執行。 – 2010-09-22 23:05:17

+1

@j_random:不是「if and only if」部分指的是$ _是否被用作從句柄讀取的行的位置,而不是定義的邏輯被採用? – Ether 2010-09-22 23:15:25

+0

你是絕對正確的,我的閱讀理解能力很差。我很抱歉。我仍然認爲明確自動應用define的時間並不會受到影響。我的猜測是:如果循環條件測試是''或者RHS上的''的標量賦值 - 是不是每件事都是? – 2010-09-23 01:37:47

1

雖然是正確的的while (my $line=<$fh>) { ... }形式得到compiledwhile (defined(my $line = <$fh>)) { ... }考慮有各種各樣的時候,值「0」的合法讀,如果你沒有在循環中明確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警告。前兩個有他們自己的問題...

相關問題