2010-06-11 14 views
13

He estado trabajando en un visor de registro para una aplicación de Rails y he descubierto que necesito leer alrededor de 200 líneas de un archivo de registro de abajo hacia arriba en lugar de hacerlo de forma predeterminada de arriba a abajo.¿Cómo leer un archivo de abajo hacia arriba en Ruby?

Los archivos de registro pueden ser bastante grandes, por lo que ya he probado y descartado el método IO.readlines ("log_file.log") [- 200 ..- 1].

¿Hay alguna otra manera de leer un archivo al revés en Ruby sin la necesidad de un complemento o joya?

+0

Duplicado de: [? La lectura de las últimas n líneas de un archivo en Ruby] (http://stackoverflow.com/questions/754494) – hippietrail

Respuesta

17

La única forma correcta de hacerlo que también funciona en archivos enormes es leer n bytes a la vez desde el final hasta que tenga el número de líneas que desea. Esto es esencialmente cómo funciona Unix tail.

Un ejemplo de implementación de IO#tail(n), que devuelve los últimos n líneas como un Array:

class IO 
    TAIL_BUF_LENGTH = 1 << 16 

    def tail(n) 
    return [] if n < 1 

    seek -TAIL_BUF_LENGTH, SEEK_END 

    buf = "" 
    while buf.count("\n") <= n 
     buf = read(TAIL_BUF_LENGTH) + buf 
     seek 2 * -TAIL_BUF_LENGTH, SEEK_CUR 
    end 

    buf.split("\n")[-n..-1] 
    end 
end 

La aplicación es un poco ingenuo, pero una referencia rápida demuestra la diferencia ridícula esta sencilla aplicación ya se puede hacer (probado con un archivo de 25 MB ~ generado con yes > yes.txt):

      user  system  total  real 
f.readlines[-200..-1] 7.150000 1.150000 8.300000 ( 8.297671) 
f.tail(200)    0.000000 0.000000 0.000000 ( 0.000367) 

El código de referencia:

require "benchmark" 

FILE = "yes.txt" 

Benchmark.bmbm do |b| 
    b.report "f.readlines[-200..-1]" do 
    File.open(FILE) do |f| 
     f.readlines[-200..-1] 
    end 
    end 

    b.report "f.tail(200)" do 
    File.open(FILE) do |f| 
     f.tail(200) 
    end 
    end 
end 

Por supuesto, other implementations ya existen. No he probado ninguno, así que no puedo decirte cuál es el mejor.

+0

Creo que significa 'TAIL_BUF_LENGTH = 2 ** 16' o '1 << 16', que se evalúan a' 65536' (64Ki). '2^16' es binario exclusivo-o y se evalúa como' 18'. –

+0

¡Funciona muy bien! La diferencia de referencia es una locura en comparación con las líneas de lectura. ¿Es posible también dar salida al número de línea correspondiente para cada línea en la matriz resultante? Gracias! – ericalli

+0

@ two2twelve: No, no lo es. El * propósito completo * de este ejercicio completo es leer el archivo "de abajo hacia arriba". (Sus palabras, no las mías.) ¿Cómo sabría en qué línea (que se cuenta desde la * cima * del archivo) usted es, si comenzó en el * fondo *? ¿O quisiste contar de abajo hacia arriba? En ese caso, es fácil: la línea en el índice 'i' en el búfer es la línea' n-i' th desde abajo. –

3

Hay un módulo Elif disponible (un puerto de Perl's File::ReadBackwards) que hace una eficiente lectura de archivos hacia atrás línea por línea.

0

Como soy muy nuevo para comentar la increíble respuesta de molf, tengo que publicarla como una respuesta separada. Necesitaba esta característica para leer los archivos de registro mientras están escritos, y la última parte de los registros contiene la cadena que necesito saber que está hecha y puedo comenzar a analizarla.

Por lo tanto, el manejo de archivos de pequeño tamaño es crucial para mí (podría hacer ping al registro mientras es muy pequeño). Así que mejorado código molf:

class IO 
    def tail(n) 
     return [] if n < 1 
     if File.size(self) < (1 << 16) 
      tail_buf_length = File.size(self) 
      return self.readlines.reverse[0..n-1] 
     else 
      tail_buf_length = 1 << 16 
     end 
     self.seek(-tail_buf_length,IO::SEEK_END) 
     out = "" 
     count = 0 
     while count <= n 
      buf  = self.read(tail_buf_length) 
      count += buf.count("\n") 
      out  += buf 
      # 2 * since the pointer is a the end , of the previous iteration 
      self.seek(2 * -tail_buf_length,IO::SEEK_CUR) 
     end 
     return out.split("\n")[-n..-1] 
    end 
end 
Cuestiones relacionadas