2010-09-22 9 views
14

lo general bucle a través de líneas en un archivo utilizando el siguiente código:¿Cuál es la forma más defensiva de recorrer líneas en un archivo con Perl?

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

Sin embargo, in answering another question, Evan Carroll editado mi respuesta, cambiar mi declaración while a:

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

Su razonamiento era que si tiene una línea que es 0 (tendría que ser la última línea, de lo contrario tendría un retorno de carro) entonces su while se cerraría prematuramente si usara mi extracto ($line se establecería en "0", y el valor de retorno de la asignación también sería "0" que se evalúa como falso). Si comprueba la definición, entonces no se encontrará con este problema. Tiene perfecto sentido.

Así que lo probé. Creé un archivo de texto cuya última línea es 0 sin retorno de carro. Lo ejecuté a través de mi loop y el loop no salió prematuramente.

Pensé: "Aja, tal vez el valor no sea en realidad 0, ¡tal vez hay algo más que está arruinando las cosas!" Así que utilicé Dump() de Devel::Peek y esto es lo que me dio:

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

que parece decirme que el valor es en realidad la cadena "0", como llegue a un resultado similar si llamo Dump() en un escalar I' ve establecido explícitamente en "0" (la única diferencia está en el campo LEN - del archivo LEN es 80, mientras que desde el escanero LEN es 8).

¿Cuál es el problema? ¿Por qué mi lazo while() no sale prematuramente si le paso una línea que es solo "0" sin retorno de carro? ¿Es el circuito de Evan realmente más defensivo, o Perl hace algo loco internamente, lo que significa que no tiene que preocuparse por estas cosas y while() realmente solo sale cuando toca eof?

+1

Si está buscando escribir un código defensivo, use un [tanque] (http://en.wikipedia.org/wiki/Tank). –

+3

Es por eso que no editaría el significado de la respuesta de alguien (solo corrijo errores tipográficos obvios). Agregue un comentario en su lugar si cree que falta algo o podría mejorarse. ¡Y felicitaciones a usted por investigar los aspectos internos! – Ether

Respuesta

18

Debido

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

compila realmente abajo a

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

Puede que haya sido necesario en una versión muy antigua de Perl, pero no más! Puede ver esto ejecutando B :: Deparse en su secuencia de comandos:

>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 

¡Ya está listo para empezar!

+1

PD, me encanta ... me encanta cómo '...' es la sintaxis válida en 5.12 y posteriores. Quiéralo. –

+0

Oh mi. Me pregunto si alguien será mordido en el culo por ese 'definido 'implícito. – zigdon

+0

Si alguien está realmente escribiendo un código que comprueba si una línea de ed de un-chomp() de un archivo sin un final de línea se evalúa como False de esa manera, está obteniendo exactamente lo que se merece. La actitud DWIM de Perl generalmente hace las cosas bien. – geoffspear

13

BTW, esto se cubre en la sección I/Operadores O de perldoc perlop:

En contexto escalar, la evaluación de un gestor de archivo en soportes de ángulo se obtiene la siguiente línea de ese archivo (el salto de línea, en su caso, incluido) o "undef" al final del archivo o en caso de error. Cuando $/se establece en "undef" (a veces conocido como modo de extracción de archivos) y el archivo está vacío, se devuelve "la primera vez", seguido de "undef" posteriormente.

Normalmente, debe asignar el valor devuelto a una variable, pero hay una situación en la que ocurre una asignación automática.Si y solo si el símbolo de entrada es lo único dentro del condicional de una declaración "while" (incluso si está disfrazada como un bucle "for (;;)"), el valor se asigna automáticamente a la variable global $ _, destruyendo cualquier estaba allí anteriormente. (Esto puede parecer extraño para usted, pero usará el constructo en casi todos los scripts de Perl que escriba). La variable $ _ no está localizada implícitamente. Tendrás que poner un "local $ _;" antes del ciclo si quieres que eso suceda.

Las siguientes líneas son equivalentes:

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

Esto también comporta de manera similar, pero evita $ _:

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

En estas construcciones de bucle, el valor asignado (si la asignación es automática o explícita) luego se prueba para ver si está definido. La prueba definida evita problemas donde la línea tiene un valor de cadena que Perl podría tratar como falso, por ejemplo, un "" o un "0" sin una línea nueva posterior. Si realmente quieren decir para tales valores a terminar el bucle, deben hacerse la prueba de forma explícita:

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

En otros contextos booleanos, "< gestor de archivo >" sin una explícita "definido" prueba o comparación provocan una advertencia si el El pragma "usar advertencias" o el modificador de línea de comandos -w (la variable $^W) está en efecto.

+1

Buena respuesta, así que borré la mía. Pero los documentos de Perl son engañosos: dicen "Si ** y solo si ** el símbolo de entrada es lo único dentro del condicional de una instrucción while" - pero luego contradicen la parte "y solo si" al mostrar que 'while (my $ line = )' también se comporta de la misma manera. Dejándonos preguntándonos exactamente en qué circunstancias se realizará este DWIMmery. –

+1

@j_random: No es la parte "si y solo si" que se refiere a si $ _ se usa como la ubicación de la línea leída desde el identificador, no si el la lógica 'defined' se emplea? – Ether

+0

Tienes toda la razón, poca comprensión de lectura por mi parte. Mis disculpas. Todavía creo que no estaría de más ser explícito acerca de cuándo 'define' se aplica automáticamente. Mi conjetura es: si la prueba condicional de bucle es '' o una asignación escalar con '' en el RHS, ¿eso es todo? –

1

Si bien es cierto que la forma de while (my $line=<$fh>) { ... } consigue compiled-while (defined(my $line = <$fh>)) { ... } consideran que hay una variedad de ocasiones en que una lectura legítima del valor "0" es malinterpretado si no tiene una explícita defined en el bucle o probando la devolución de <>.

Éstos son algunos ejemplos:

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

No estoy diciendo que estas son buenas formas de Perl! Estoy diciendo que son posibles; especialmente las fallas 3,4 y 5. Anote la falla sin advertencia Perl en el número 4 y 5. Las dos primeras tienen sus propios problemas ...

Cuestiones relacionadas