¿Cuál es la diferencia entre iterar sobre un archivo con foreach o mientras está en Perl?

Tengo un FILE Filehandle en Perl, y quiero iterar sobre todas las líneas en el archivo. ¿Hay alguna diferencia entre los siguientes?

 while () { # do something } 

y

 foreach () { # do something } 

Para la mayoría de los propósitos, probablemente no notarás la diferencia. Sin embargo, foreach lee cada línea en una lista ( no en una matriz ) antes de recorrerla línea por línea, mientras while lee una línea a la vez. Dado que foreach utilizará más memoria y requerirá tiempo de procesamiento por adelantado, generalmente se recomienda usar while para recorrer líneas de un archivo.

EDIT (vía Schwern): El ciclo foreach es equivalente a esto:

 my @lines = < $fh>; for my $line (@lines) { ... } 

Es lamentable que Perl no optimice este caso especial como lo hace con el operador de rango ( 1..10 ).

Por ejemplo, si leo / usr / share / dict / words con un ciclo for y un ciclo while y hago que duerman cuando terminen, puedo usar ps para ver cuánta memoria está consumiendo el proceso. Como control, he incluido un progtwig que abre el archivo pero no hace nada con él.

 USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND schwern 73019 0.0 1.6 625552 33688 s000 S 2:47PM 0:00.24 perl -wle open my $fh, shift; for(< $fh>) { 1 } print "Done"; sleep 999 /usr/share/dict/words schwern 73018 0.0 0.1 601096 1236 s000 S 2:46PM 0:00.09 perl -wle open my $fh, shift; while(< $fh>) { 1 } print "Done"; sleep 999 /usr/share/dict/words schwern 73081 0.0 0.1 601096 1168 s000 S 2:55PM 0:00.00 perl -wle open my $fh, shift; print "Done"; sleep 999 /usr/share/dict/words 

El progtwig for consume casi 32 megas de memoria real (la columna RSS ) para almacenar el contenido de mis 2,4 meg / usr / share / dict / words. El ciclo while solo almacena una línea a la vez consumiendo solo 70k para el almacenamiento en línea.

En contexto escalar (es decir, while ) devuelve cada línea por turno.

En contexto de lista (es decir, foreach ) devuelve una lista que consta de cada línea del archivo.

Deberías usar el constructo while .

Ver perlop – Operadores de E / S para más.

Editar: j_random_hacker correctamente dice que

 while () { … } 

pisotea $_ mientras foreach no (foreach localiza $_ primero). ¡Seguramente esta es la diferencia de comportamiento más importante!

Además de las respuestas anteriores, otro beneficio de usar while es que puedes usar $. variable. Este es el número de línea actual del último perldoc perlvar accedió (ver perldoc perlvar ).

 while ( my $line =  ) { if ( $line =~ /some_target/ ) { print "Found some_target at line $.\n"; } } 

Agregué un ejemplo sobre esto a la próxima edición de Effective Perl Programming .

Con un while , puede detener el procesamiento de FILE y obtener las líneas no procesadas:

  while(  ) { # scalar context last if ...; } my $line = ; # still lines left 

Si usa un foreach , consume todas las líneas del foreach incluso si deja de procesarlas:

  foreach(  ) { # list context last if ...; } my $line = ; # no lines left! 

Actualización: j al azar hacker señala en un comentario que Perl especializa la prueba de falsedad en un ciclo while cuando lee desde un manejador de archivo. Acabo de verificar que leer un valor falso no terminará el ciclo, al menos en perls modernos. Perdón por dirigirlos mal. Después de 15 años de escribir Perl, sigo siendo un novato. 😉

Todos los de arriba están en lo cierto: usa el ciclo while porque será más eficiente con la memoria y te dará más control.

Sin embargo, una cosa graciosa sobre ese ciclo while es que sale cuando la lectura es falsa. Por lo general, eso será al final del archivo, pero ¿y si devuelve una cadena vacía o un 0? Oops! Su progtwig acaba de salir demasiado pronto. Esto puede suceder en cualquier manejador de archivo si la última línea del archivo no tiene una nueva línea. También puede ocurrir con objetos de archivos personalizados que tienen un método de lectura que no trata las líneas nuevas de la misma manera que los objetos de archivos Perl normales.

He aquí cómo solucionarlo. Compruebe si hay una lectura de valor indefinido que indique el final del archivo:

 while (defined(my $line = )) { print $line; } 

El ciclo foreach no tiene este problema por cierto y es correcto aunque ineficiente.

j_random_hacker mencionó esto en los comentarios a esta respuesta , pero en realidad no lo puso en una respuesta propia, a pesar de que es otra diferencia que vale la pena mencionar.

La diferencia es que while () {} sobrescribe $_ , mientras que foreach() {} localiza. Es decir:

 $_ = 100; while () { # $_ gets each line in turn # do something with the file } print $_; # yes I know that $_ is unneeded here, but # I'm trying to write clear code for the example 

Imprimirá la última línea de .

Sin embargo,

 $_ = 100; foreach() { # $_ gets each line in turn # do something with the file } print $_; 

Se imprimirá 100 . Para obtener lo mismo con un constructo while() {} , necesitarías hacer:

 $_ = 100; { local $_; while () { # $_ gets each line in turn # do something with the file } } print $_; # yes I know that $_ is unneeded here, but # I'm trying to write clear code for the example 

Ahora esto imprimirá 100 .

Aquí hay un ejemplo donde foreach no funcionará, pero while hará el trabajo

 while () { $line1 = $_; if ($line1 =~ /SOMETHING/) { $line2 = ; if (line2 =~ /SOMETHING ELSE/) { print "I found SOMETHING and SOMETHING ELSE in consecutive lines\n"; exit(); } } } 

Simplemente no puede hacer esto con foreach porque leerá todo el archivo en una lista antes de ingresar al ciclo y no podrá leer la siguiente línea dentro del ciclo. Estoy seguro de que habrá soluciones para este problema, incluso en Foreach (leer en una matriz viene a la mente), pero definitivamente ofrece una solución muy directa.

Un segundo ejemplo es cuando tiene que analizar un archivo grande (digamos 3GB) en su máquina con solo 2GB de RAM. foreach simplemente se quedará sin memoria y se bloqueará. Aprendí esto de la manera difícil muy temprano en mi vida de progtwigción perl.

el bucle foreach es más rápido que while (que está basado en condiciones).