2010-05-28 4 views
8

Estoy tratando de escribir un fragmento de código que lea un archivo línea por línea y almacene cada línea, hasta una cierta cantidad de datos de entrada. Quiero protegerme de que el usuario final sea malvado y poner algo así como una serie de datos en una línea, además de protegerme contra la succión de un archivo anormalmente grande. Haciendo $str = <FILE> leerá en una línea completa, y eso podría ser muy largo y explotar mi memoria.En Perl, ¿puedo limitar la longitud de una línea cuando la leo desde un archivo (como fgets)?

fgets me permite hacer esto al permitirme especificar un número de bytes para leer durante cada llamada y, básicamente, me permite dividir una larga línea en mi longitud máxima. ¿Hay alguna manera similar de hacer esto en perl? Vi algo sobre sv_gets pero no estoy seguro de cómo usarlo (aunque solo hice una búsqueda rápida en Google).

El objetivo de este ejercicio es evitar tener que realizar análisis/almacenamiento en búfer adicionales después de leer los datos. fgets se detiene después de N bytes o cuando se alcanza una nueva línea.

EDIT Creo que confundí un poco. Quiero leer líneas X, cada una con una longitud máxima Y. No quiero leer más de Z bytes en total, y preferiría no leer todos los bytes Z a la vez. Creo que podría hacer eso y dividir las líneas, pero me pregunto si hay alguna otra manera. Si esa es la mejor manera, entonces usar la función de lectura y hacer un análisis manual es mi apuesta más fácil.

Gracias.

+0

¿por qué preferiría no leer todos los bytes Z a la vez? ¿Estás buscando una función get_n_lines_or_max_bytes (fh, n, z)? Tal bestia no es muy difícil de escribir ... – geocar

+0

Supongo que es solo cuestión de preferencia. Odio chupar megs de datos cuando puedo analizarlos de forma incremental. Además, ignoraré algunos datos también, entonces ¿por qué ocupar la memoria innecesaria al principio? Sin embargo, veo que es una solución para facilitar el mantenimiento/escritura. –

+0

@SB: pruébelo. Encontrará que llamar a read() y split() usa menos memoria y se ejecuta más rápido que utilizando cualquiera de las implementaciones de fgets(). – geocar

Respuesta

5

Perl no tiene incorporados los datos, pero File::GetLineMaxLength lo implementa.

Si quiere hacerlo usted mismo, es bastante sencillo con getc.

sub fgets { 
    my($fh, $limit) = @_; 

    my($char, $str); 
    for(1..$limit) { 
     my $char = getc $fh; 
     last unless defined $char; 
     $str .= $char; 
     last if $char eq "\n"; 
    } 

    return $str; 
} 

La concatenación de cada personaje para $str es eficiente como Perl realloc oportunista. Si una cadena Perl tiene 16 bytes y concatenas otro personaje, Perl lo reasignará a 32 bytes (32 va a 64, 64 a 128 ...) y recordará la longitud. Las siguientes 15 concatenaciones no requieren reasignaciones de memoria o llamadas a strlen.

+1

Creo que esto está limpio, y vi otra de tus respuestas que hablaba sobre la asignación previa de una cadena en Perl. La combinación de los dos elimina las ineficiencias (si las hay) de la reasignación constante ya que solo necesito asignar la longitud máxima una vez. –

+0

Gracias. No creo que la preasignación te compre mucho. De hecho, probablemente sea más lento, ya que es probable que sea más lento preasignar una cadena en Perl que dejar que perl lo haga. También desperdiciará mucha memoria ya que cada cadena usará la memoria máxima. Benchmarking confirma esto. Si realmente quieres que esto sea lo más rápido posible, escribe un contenedor XS alrededor de fgets(). Es bastante trivial (según los estándares XS). – Schwern

+0

Lo que quise decir es que preasignificamos la cadena fuera de las llamadas a fgets y pasamos por referencia a tus datos para agregar. Aunque no estoy seguro de lo que sucede cuando asigno la cadena a otra. También podría dejar que se asigne –

1

Uso del read function (perlfunc leer)

+0

La belleza de los fgets es que o lee N datos o se detiene en una nueva línea. No creo que la lectura se detenga en una nueva línea. –

4
sub heres_what_id_do($$) { 
    my ($fh, $len) = @_; 
    my $buf = ''; 

    for (my $i = 0; $i < $len; ++$i) { 
     my $ch = getc $fh; 
     last if !defined $ch || $ch eq "\n"; 
     $buf .= $ch; 
    } 

    return $buf; 
} 

No muy "Perlish", pero a quién le importa? :) El sistema operativo (y posiblemente el propio Perl) hará todo el almacenamiento en memoria intermedia necesario debajo.

+1

'== '\ n'' debe ser' eq "\ n" '. 'getc' hace esto mucho más simple que usar' read' para obtener un solo caracter. El benchmarking muestra que es más lento que el mío en aproximadamente un 15%. Curiosamente, el 3 arg for es significativamente más rápido que 'para mi $ i (0 .. $ len-1)' pero no para 'my $ i; my $ end = $ len-1; para $ i (0 .. $ len) '(lo pone a la par con el mío) lo que indica que la optimización del iterador' de Perl (para ... $ foo) 'se derrota fácilmente. – Schwern

+0

Gracias por editar Schwern. Es vergonzoso, pero no sabía que Perl realmente tiene 'getc()'! Editará para usar eso. –

-2

Puede implementar fgets() usted trivialmente. Aquí hay una que works like C:

sub fgets{my($n,$c)=($_[1],''); ($_[0])=(''); 
    for(;defined($c)&&$c ne "\n"&&$n>0;$n--){$_[0].=($c=getc($_[2]));} 
    defined($c)&&$_[0]; } 

Aquí está uno con semantics de PHP:

sub fgets{my($n,$c,$x)=($_[1],'',''); 
    for(;defined($c)&&$c ne "\n"&&$n>0;$n--){$x.=($c=getc($_[0]));} 
    ($x ne '')&&$x; } 

Si usted está tratando de poner en práctica los límites de recursos (es decir, tratando de evitar que un cliente no es de confianza de comer a toda la memoria) se realmente no debería hacerlo de esta manera. Use ulimit para configurar esos límites de recursos antes de llamar a su script. Un buen administrador de sistemas establecerá los límites de recursos de todos modos, pero les gusta cuando los programadores crean scripts de inicio que establecen límites razonables.

Si intenta limitar la entrada antes de enviar estos datos a otro sitio (por ejemplo, limitando las líneas de entrada SMTP porque sabe que los sitios remotos podrían no admitir más de 511 caracteres), simplemente verifique la longitud de la línea después <INPUT> con length().

+0

No se puede ... comprender ... ¡código! Lanza una advertencia a eof porque se concatena antes de comprobar si se define $ c. Si bien refleja los objetos de C muy admirablemente, no es muy perlado. A pesar de su inescrutabilidad, no es más rápido que el mío o j_random. – Schwern

+0

@Schwem: Entonces 'no strict 'si le molesta. – geocar

3

Como ejercicio, he implementado una envoltura alrededor de la función fgets() de C. Se trata de una implementación de Perl para manejadores de archivos complicados definidos como "cualquier cosa sin un nombre de archivo" para cubrir identificadores atados y otras cosas. File::fgets está en camino hacia CPAN ahora, puede extraer una copia del repositorio.

Algunos benchmarking básicos muestran que son 10 veces más rápidos que cualquiera de las implementaciones aquí. Sin embargo, no puedo decir que esté libre de errores o que no pierda memoria, mis habilidades XS no son tan buenas, pero es mejor probado que nada aquí.

Cuestiones relacionadas