2009-04-16 9 views

Respuesta

4

No puedo responder por Ruby pero la mayoría de estos lenguajes siguen el lenguaje C de E/S de archivos. Eso significa que no hay forma de hacer lo que preguntas aparte de buscar. Esto generalmente toma uno de dos enfoques.

  • Comenzando desde el inicio del archivo y escaneándolo todo, recordando las 25 líneas más recientes. Luego, cuando llegue al final del archivo, imprímalo.
  • Un enfoque similar pero tratando de buscar primero una ubicación con mejor estimación. Eso significa buscar (por ejemplo) fin de archivo menos 4000 caracteres, y luego hacer exactamente lo que hizo en el primer acercamiento, con la condición de que, si no obtuvo 25 líneas, debe hacer una copia de seguridad y volver a intentarlo (por ejemplo, al final del archivo menos 5000 caracteres).

La segunda forma es la que yo prefiero ya que, si elige su primera compensación sabiamente, casi seguramente solo necesitará una oportunidad. Los archivos de registro todavía tienden a tener longitudes de línea máximas fijas (creo que los codificadores todavía tienen una propensión a los archivos de 80 columnas mucho después de que su utilidad se haya degradado). Tiendo a elegir el número de líneas deseado multiplicado por 132 como mi desplazamiento.

Y a partir de una mirada rápida de los documentos de Ruby en línea, parece que hace siguiendo el modismo C. Utilizaría "ios.seek(25*-132,IO::SEEK_END)" si siguiera mi consejo, luego lea desde allí.

+0

Todos mis terminales y emacs tampones son todavía 80 columnas de ancho; eso me permite colocar varios al lado del otro en mi monitor, lo cual es muy útil. –

+0

Estoy bastante seguro de que la búsqueda de IO # va a ser la solución óptima, en cuanto a rendimiento. –

24

¿El archivo es lo suficientemente grande como para evitar leerlo todo? Si no es así, usted podría hacer

IO.readlines("file.log")[-25..-1] 

Si es demasiado grande, es posible que necesite usar IO#seek para leer de cerca del final del archivo, y seguir buscando hacia el principio hasta que haya visto 25 líneas.

+2

Si no quiere pasar por la molestia de revertirlo, puede usar [-25 ..- 1] en su lugar. – sris

+0

Beaty. No es necesario asumir nada sobre los comandos del sistema disponibles de esta manera. Gracias. :) –

+1

@sris problema con [-25 ..- 1], si el archivo tiene menos de 25 líneas, entonces el resultado es nulo, recomendaría usar 'IO.readlines (" file.log "). Last (25) 'que devuelve matriz vacía en ese caso. –

13

Hay una biblioteca para Ruby llamada File::Tail. Esto puede obtener las últimas N líneas de un archivo como la utilidad de cola UNIX.

Asumo que hay algunos buscan la optimización en su lugar en la versión de UNIX de cola con puntos de referencia como estos (probado en un archivo de texto poco más de 11M):

[[email protected]]$du -sh 11M.txt 
11M  11M.txt 
[[email protected]]$time tail -n 25 11M.txt 
/sbin/ypbind 
/sbin/arptables 
/sbin/arptables-save 
/sbin/change_console 
/sbin/mount.vmhgfs 
/misc 
/csait 
/csait/course 
/.autofsck 
/~ 
/usb 
/cdrom 
/homebk 
/staff 
/staff/faculty 
/staff/faculty/darlinr 
/staff/csadm 
/staff/csadm/service_monitor.sh 
/staff/csadm/.bash_history 
/staff/csadm/mysql5 
/staff/csadm/mysql5/MySQL-server-community-5.0.45-0.rhel5.i386.rpm 
/staff/csadm/glibc-common-2.3.4-2.39.i386.rpm 
/staff/csadm/glibc-2.3.4-2.39.i386.rpm 
/staff/csadm/csunixdb.tgz 
/staff/csadm/glibc-headers-2.3.4-2.39.i386.rpm 

real 0m0.012s 
user 0m0.000s 
sys  0m0.010s 

Sólo puedo imaginar la librería Ruby utiliza una método similar.

Editar:

para la curiosidad de Pax:

[[email protected]]$time cat 11M.txt | tail -n 25 
/sbin/ypbind 
/sbin/arptables 
/sbin/arptables-save 
/sbin/change_console 
/sbin/mount.vmhgfs 
/misc 
/csait 
/csait/course 
/.autofsck 
/~ 
/usb 
/cdrom 
/homebk 
/staff 
/staff/faculty 
/staff/faculty/darlinr 
/staff/csadm 
/staff/csadm/service_monitor.sh 
/staff/csadm/.bash_history 
/staff/csadm/mysql5 
/staff/csadm/mysql5/MySQL-server-community-5.0.45-0.rhel5.i386.rpm 
/staff/csadm/glibc-common-2.3.4-2.39.i386.rpm 
/staff/csadm/glibc-2.3.4-2.39.i386.rpm 
/staff/csadm/csunixdb.tgz 
/staff/csadm/glibc-headers-2.3.4-2.39.i386.rpm 

real 0m0.350s 
user 0m0.000s 
sys  0m0.130s 

todavía en un segundo, pero si hay una gran cantidad de operaciones de archivo esto hace una gran diferencia.

+1

¿Qué le proporciona "cat 11M.txt | tail -n 25"? Eso obligará a la cola a procesar toda la secuencia. – paxdiablo

+0

O simplemente cat 11M.txt>/dev/null para ese asunto, eso le dará tiempo para procesar la transmisión, que bien puede ser del orden de 1/100 de segundo. – paxdiablo

+0

Bugbear mío, @JohnT: "29 veces más lento" de 100 segundos es -2800 segundos. La frase correcta es "aproximadamente 1/29 de la velocidad". Pero entiendo tu punto: claramente tail está utilizando un método de búsqueda cuando tiene el archivo en lugar de una transmisión. Uno esperaría que Ruby sea tan inteligente también. – paxdiablo

35

Si en un sistema * nix con tail, puede engañar de esta manera:

last_25_lines = `tail -n 25 whatever.txt` 
+1

Creo que una biblioteca sería más suficiente para la capacidad de plataforma cruzada, pero entiendes la idea. –

+1

Probablemente cierto. No ejecuto código de Ruby en nada que no sea * sistemas basados ​​en * nix y creo que tendrás dificultades para encontrar uno de esos sin 'cola' ... Además, a veces no puedes darte el lujo de instalar una biblioteca. Solo quería mostrar el 'one liner' :) – rfunduk

+0

Hasta ahora no me he desplegado en un entorno que no tiene cola. Esta respuesta debe ser aceptada en mi opinión. –

1

¿Qué tal:

file = [] 
File.open("file.txt").each_line do |line| 
    file << line 
end 

file.reverse.each_with_index do |line, index| 
    puts line if index < 25 
end 

El rendimiento sería terrible sobre un archivo grande como se itera dos veces, el mejor enfoque sería el ya mencionado leer el archivo y almacenar las últimas 25 líneas en la memoria y mostrarlas. Pero esto fue solo un pensamiento alternativo.

7

acabo escribió un implemenation rápido con #seek:

class File 
    def tail(n) 
    buffer = 1024 
    idx = (size - buffer).abs 
    chunks = [] 
    lines = 0 

    begin 
     seek(idx) 
     chunk = read(buffer) 
     lines += chunk.count("\n") 
     chunks.unshift chunk 
     idx -= buffer 
    end while lines < n && pos != 0 

    chunks.join.lines.reverse_each.take(n).reverse.join 
    end 
end 

File.open('rpn-calculator.rb') do |f| 
    p f.tail(10) 
end 
+1

En realidad, aunque tu código basado en búsquedas está cerca, no es del todo correcto porque no elimina la parte del fragmento anterior al primero \ n. Vea la nueva respuesta a continuación. :) –

+0

: 11: en 'cola ': método indefinido' count' para nil: NilClass (NoMethodError) – Istvan

7

Versión mejorada del excelente solución basada en buscar de manveru. Este devuelve exactamente n líneas.

class File 

    def tail(n) 
    buffer = 1024 
    idx = [size - buffer, 0].min 
    chunks = [] 
    lines = 0 

    begin 
     seek(idx) 
     chunk = read(buffer) 
     lines += chunk.count("\n") 
     chunks.unshift chunk 
     idx -= buffer 
    end while lines < (n + 1) && pos != 0 

    tail_of_file = chunks.join('') 
    ary = tail_of_file.split(/\n/) 
    lines_to_return = ary[ ary.size - n, ary.size - 1 ] 

    end 
end 
+0

Ese código funciona en una Mac, pero falla en Linux con un mensaje de error "' cola ': variable local indefinida o método ' tamaño'". ¿Hay alguna idea de cómo arreglar eso? – earlyadopter

+1

No hay verificación consolidada, lo que significa que puede leer y terminar buscando un idx negativo. Además, esto no está optimizado muy bien para un archivo con líneas muy largas (es decir, guardando los fragmentos en un búfer predestinado). Publicada una versión que se encarga de ambos. – Shai

4

Aquí hay una versión de la cola que no almacena ningún buffers en la memoria, mientras que ir, pero en su lugar utiliza "punteros". También hace la verificación de límite para que no termine buscando un desplazamiento negativo (si, por ejemplo, tiene más para leer pero menos que su tamaño de fragmento).

def tail(path, n) 
    file = File.open(path, "r") 
    buffer_s = 512 
    line_count = 0 
    file.seek(0, IO::SEEK_END) 

    offset = file.pos # we start at the end 

    while line_count <= n && offset > 0 
    to_read = if (offset - buffer_s) < 0 
       offset 
       else 
       buffer_s 
       end 

    file.seek(offset-to_read) 
    data = file.read(to_read) 

    data.reverse.each_char do |c| 
     if line_count > n 
     offset += 1 
     break 
     end 
     offset -= 1 
     if c == "\n" 
     line_count += 1 
     end 
    end 
    end 

    file.seek(offset) 
    data = file.read 
end 

casos de prueba en https://gist.github.com/shaiguitar/6d926587e98fc8a5e301

+0

Esto no cierra el archivo. –

Cuestiones relacionadas