mapas manipulacion manejo herencias clase cadenas archivos ruby file-io

manipulacion - require en ruby



¿Lectura de las últimas n líneas de un archivo en Ruby? (8)

Necesito leer las últimas 25 líneas de un archivo (para mostrar las entradas de registro más recientes). ¿Hay alguna forma en Ruby para comenzar al final de un archivo y leerlo al revés?


¿Es el archivo lo suficientemente grande como para evitar leer todo? Si no, puedes hacer

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

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


Acabo de escribir una implementación rápida 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


Aquí hay una versión de cola que no almacena ningún búfer en la memoria mientras que usted va, sino que usa "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


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.

Supongo que hay alguna optimización de búsqueda en la versión de cola de UNIX con puntos de referencia como estos (probados en un archivo de texto de poco más de 11M):

[john@awesome]$du -sh 11M.txt 11M 11M.txt [john@awesome]$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

Solo puedo imaginarme que la biblioteca de Ruby usa un método similar.

Editar:

por la curiosidad de Pax:

[john@awesome]$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

aún en menos de un segundo, pero si hay muchas operaciones de archivos, esto hace una gran diferencia.


No puedo responder por Ruby, pero la mayoría de estos idiomas siguen el idioma 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 una ubicación de mejor estimación primero. 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 superficial de los documentos de Ruby en línea, parece que sigue el modismo de C. Deberías usar "ios.seek(25*-132,IO::SEEK_END)" si fueras a seguir mi consejo, luego leer desde allí.


Qué tal si:

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 horrible en un archivo grande ya que 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.


Versión mejorada de la excelente solución basada en búsquedas 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


Si está en un sistema * nix con tail , puede hacer trampa de esta manera:

last_25_lines = `tail -n 25 whatever.txt`