2010-09-22 12 views
14

Io di solito un ciclo tra le linee in un file utilizzando il seguente codice:Qual è il modo più difensivo per scorrere le righe in un file con Perl?

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

Tuttavia, in answering another question, Evan Carroll modificato la mia risposta, cambiando il mio while dichiarazione:

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

La sua logica era che se si avere una linea che è 0 (dovrebbe essere l'ultima riga, altrimenti avrebbe un ritorno a capo) quindi il tuo while uscirebbe prematuramente se hai usato la mia dichiarazione ($line sarebbe impostato su "0" e il valore restituito dall'assegnazione sarebbe quindi "0" che viene valutato su falso). Se controlli la definizione, non ti imbatti in questo problema. Ha perfettamente senso.

Quindi l'ho provato. Ho creato un file di testo la cui ultima riga è 0 senza alcun ritorno a capo su di esso. L'ho passato attraverso il mio ciclo e il ciclo non si è interrotto prematuramente.

Poi ho pensato, "Aha, forse il valore non è in realtà 0, forse c'è qualcos'altro che sta rovinando tutto!" Così ho usato Dump() da Devel::Peek e questo è quello che mi ha dato:

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

che sembra dire a me che il valore è in realtà la stringa "0", mentre ottengo un risultato simile se chiamo Dump() su un tipo scalare I' ve impostato esplicitamente su "0" (l'unica differenza è nel campo LEN - dal file LEN è 80, mentre dal LEN scalare è 8).

Allora, qual è l'affare? Perché il mio ciclo while() non si chiude prematuramente se si passa una linea che è solo "0" senza ritorno a capo? Il loop di Evan è in realtà più difensivo, o Perl fa qualcosa di pazzo internamente che significa che non devi preoccuparti di queste cose e while() in realtà esce solo quando colpisci eof?

+1

Se stai cercando di scrivere un codice difensivo, usa un [serbatoio] (http://en.wikipedia.org/wiki/Tank). –

+3

Questo è il motivo per cui non modificherei il significato della risposta di qualcuno (correggo solo errori evidenti). Aggiungi un commento, se pensi che manchi qualcosa o che possa essere migliorato. E complimenti a te per aver indagato sugli interni! – Ether

risposta

18

Perché

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

compila in realtà fino a

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

Potrebbe essere stato necessaria in una versione molto vecchia di Perl, ma ora non più! Si può vedere questo esecuzione B :: Deparse sul tuo script:

>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 

Quindi sei già pronti per partire!

+1

PS, mi piace ... assolutamente AMORE come '...' è una sintassi valida in 5.12 e versioni successive. Lo adoro. –

+0

Oh mio. Mi chiedo se qualcuno venga mai morso nel culo da quell'implicito 'definito'? – zigdon

+0

Se qualcuno sta davvero scrivendo un codice che sta controllando se una riga non-chomp() edita da un file senza una fine di riga restituisce False in questo modo, ottiene esattamente ciò che merita. L'atteggiamento DWIM di Perl di solito fa le cose per bene. – geoffspear

13

BTW, questo è coperto nella sezione I/O Operatori di perldoc perlop:

In un contesto scalare, valutare un filehandle tra parentesi angolari produce la riga successiva da quel file (il ritorno a capo, se del caso, inclusi), o "undef" alla fine del file o in caso di errore. Quando $/è impostato su "undef" (a volte noto come modalità slurp di file) e il file è vuoto, restituisce "la prima volta, seguito da" undef "successivamente.

Ordinariamente è necessario assegnare il valore restituito a una variabile, ma esiste una situazione in cui si verifica un'assegnazione automatica.Se e solo se il simbolo di input è l'unica cosa all'interno del condizionale di un'istruzione "while" (anche se mascherata da un ciclo "for (;;)", il valore viene automaticamente assegnato alla variabile globale $ _, distruggendo qualsiasi cosa era lì in precedenza. (Potrebbe sembrarti strano, ma userai il costrutto in quasi tutti gli script Perl che scrivi.) La variabile $ _ non è localizzata implicitamente. Dovrai inserire un "$ locale;" prima del ciclo se vuoi che ciò accada.

Le righe seguenti sono equivalenti:

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

Questo comporta anche in modo simile, ma evita $ _:

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

In questi costrutti di loop, il valore assegnato (se l'assegnazione è automatica o esplicita) viene quindi testato per vedere se è definito. Il test definito evita i problemi in cui la riga ha un valore di stringa che sarebbe trattato come falso da Perl, ad esempio un "" o uno "0" senza newline finale. Se davvero intende per tali valori di interrompere il ciclo, essi dovrebbero essere testati per esplicitamente:

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

In altri contesti booleani, "<filehandle>" senza un esplicito "definito" test o confronto suscitano un avviso se il "use warnings" pragma o l'opzione -w della riga di comando (la variabile $^W) è in vigore.

+1

Buona risposta, quindi ho eliminato il mio. Ma i documenti Perl sono fuorvianti - dicono, "Se ** e solo se ** il simbolo di input è l'unica cosa all'interno dell'istruzione condizionale di un while" - ma in seguito contraddicono il "e solo se" parte dimostrando che 'while (my $ line = )' si comporta allo stesso modo. Lasciandoci a chiederci esattamente su quali circostanze verrà eseguito questo DWIMmery. –

+1

@j_random: non è la parte "if and only if" che si riferisce a $ _ viene utilizzato come posizione per la riga letta dall'handle, non se viene utilizzata la logica 'defined'? – Ether

+0

Hai assolutamente ragione, scarsa comprensione della lettura da parte mia. Mie scuse. Continuo a pensare che non sarebbe male essere espliciti esattamente quando 'define' viene applicato automaticamente. La mia ipotesi è: se il test condizionale del ciclo è '' o un assegnamento scalare con '' sul RHS - è tutto ciò che? –

1

Se è vero che la forma di while (my $line=<$fh>) { ... } ottiene compiled-while (defined(my $line = <$fh>)) { ... } considerare ci sono una varietà di volte in cui una lettura legittima il valore "0" viene frainteso, se non si dispone di un esplicito defined nel ciclo o test del rendimento di <>.

Ecco alcuni esempi:

#!/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: $!"; 
} 

Non sto dicendo che queste sono buone forme di Perl! Sto dicendo che sono possibili; in particolare Failure 3,4 e 5. Notare l'errore senza avviso Perl sui numeri 4 e 5. I primi due hanno i loro problemi ...

Problemi correlati