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`