¿Cuáles son todas las formas comunes de leer un archivo en Ruby?

280

¿Cuáles son todas las formas comunes de leer un archivo en Ruby?

Por ejemplo, aquí hay un método:

fileObj = File.new($fileName, "r")
while (line = fileObj.gets)
  puts(line)
end
fileObj.close

Sé que Ruby es extremadamente flexible. ¿Cuáles son los beneficios / inconvenientes de cada enfoque?

dsg
fuente
66
No creo que la respuesta ganadora actual sea correcta.
Inger

Respuestas:

259
File.open("my/file/path", "r") do |f|
  f.each_line do |line|
    puts line
  end
end
# File is closed automatically at end of block

También es posible cerrar explícitamente el archivo después de lo anterior (pase un bloque para cerrarlo openpor usted):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close
fl00r
fuente
14
Esto no es idiota Ruby. Use en foreachlugar de openy prescinda del each_linebloque.
The Tin Man
77
f.each { |line| ... }y f.each_line { |line| ... }parecen tener el mismo comportamiento (al menos en Ruby 2.0.0).
chbrown
327

La forma más fácil si el archivo no es demasiado largo es:

puts File.read(file_name)

De hecho, IO.reado File.readcierre automáticamente el archivo, por lo que no es necesario usarlo File.opencon un bloque.

mckeed
fuente
16
IO.reado File.readtambién cierra automáticamente el archivo, aunque su redacción hace que parezca que no.
Phrogz
15
él ya dijo "si el archivo no es demasiado largo". Se adapta perfectamente a mi caso.
jayP
227

Tenga cuidado con los archivos "sorber". Ahí es cuando lees todo el archivo a la memoria a la vez.

El problema es que no escala bien. Podría estar desarrollando código con un archivo de tamaño razonable, luego ponerlo en producción y de repente descubrir que está tratando de leer archivos que miden en gigabytes, y su host se está congelando mientras intenta leer y asignar memoria.

La E / S línea por línea es muy rápida y casi siempre tan efectiva como el sorber. Es sorprendentemente rápido en realidad.

Me gusta usar:

IO.foreach("testfile") {|x| print "GOT ", x }

o

File.foreach('testfile') {|x| print "GOT", x }

El archivo hereda de IO y foreachestá en IO, por lo que puede usar cualquiera.

Tengo algunos puntos de referencia que muestran el impacto de tratar de leer archivos grandes a través readde E / S línea por línea en " ¿Por qué no es una buena práctica" sorber "un archivo? ".

el hombre de hojalata
fuente
66
Esto es exactamente lo que estaba buscando. Tengo un archivo con cinco millones de líneas, y realmente no quería que se cargara en la memoria.
Scotty C.
68

Puede leer el archivo de una vez:

content = File.readlines 'file.txt'
content.each_with_index{|line, i| puts "#{i+1}: #{line}"}

Cuando el archivo es grande, o puede ser grande, generalmente es mejor procesarlo línea por línea:

File.foreach( 'file.txt' ) do |line|
  puts line
end

Sin embargo, a veces desea acceder al identificador de archivo o controlar las lecturas usted mismo:

File.open( 'file.txt' ) do |f|
  loop do
    break if not line = f.gets
    puts "#{f.lineno}: #{line}"
  end
end

En el caso de archivos binarios, puede especificar un separador nulo y un tamaño de bloque, así:

File.open('file.bin', 'rb') do |f|
  loop do
    break if not buf = f.gets(nil, 80)
    puts buf.unpack('H*')
  end
end

Finalmente, puede hacerlo sin bloqueo, por ejemplo, al procesar múltiples archivos simultáneamente. En ese caso, el archivo debe cerrarse explícitamente (mejorado según el comentario de @antinome):

begin
  f = File.open 'file.txt'
  while line = f.gets
    puts line
  end
ensure
  f.close
end

Referencias: API de archivo y la API de IO .

Victor Klos
fuente
2
No hay for_eachen Archivo o IO. Usar en su foreachlugar.
The Tin Man
1
Por lo general, uso el editor de texto sublime, con el complemento RubyMarkers, al documentar el código que se utilizará en las respuestas aquí. Hace que sea realmente fácil mostrar resultados intermedios, similar al uso de IRB. También el plugin Seeing Is Believing para Sublime Text 2 es realmente poderoso.
The Tin Man
1
Gran respuesta. Para el último ejemplo, podría sugerir usar en whilelugar de loopusar ensurepara garantizar que el archivo se cierre incluso si se produce una excepción. Como esto (reemplace punto y coma con saltos de línea): begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome
1
sí, eso es mucho mejor @antinome, mejoró la respuesta. ¡Gracias!
Victor Klos
26

Un método simple es usar readlines:

my_array = IO.readlines('filename.txt')

Cada línea en el archivo de entrada será una entrada en la matriz. El método se encarga de abrir y cerrar el archivo por usted.

bta
fuente
55
Al igual que con readcualquier variante, esto arrastrará todo el archivo a la memoria, lo que puede causar problemas importantes si el archivo es más grande que la memoria disponible. Además, debido a que es una matriz, Ruby tiene que crear la matriz, lo que ralentiza el proceso adicionalmente.
The Tin Man
9

Usualmente hago esto:

open(path_in_string, &:read)

Esto le dará todo el texto como un objeto de cadena. Funciona solo bajo Ruby 1.9.

sawa
fuente
¡Esto es bueno y corto! ¿Cierra el archivo también?
mrgreenfur
55
Lo cierra, pero no es escalable, así que tenga cuidado.
The Tin Man
3

devuelve las últimas n líneas de your_file.log o .txt

path = File.join(Rails.root, 'your_folder','your_file.log')

last_100_lines = `tail -n 100 #{path}`
Alex Danko
fuente
1

Una forma aún más eficiente es la transmisión pidiendo al núcleo del sistema operativo que abra un archivo y luego lea los bytes poco a poco. Al leer un archivo por línea en Ruby, los datos se toman del archivo 512 bytes a la vez y luego se dividen en "líneas".

Al almacenar en búfer el contenido del archivo, el número de llamadas de E / S se reduce al dividir el archivo en fragmentos lógicos.

Ejemplo:

Agregue esta clase a su aplicación como un objeto de servicio:

class MyIO
  def initialize(filename)
    fd = IO.sysopen(filename)
    @io = IO.new(fd)
    @buffer = ""
  end

  def each(&block)
    @buffer << @io.sysread(512) until @buffer.include?($/)

    line, @buffer = @buffer.split($/, 2)

    block.call(line)
    each(&block)
  rescue EOFError
    @io.close
 end
end

Llámalo y pasa el :eachmétodo un bloque:

filename = './somewhere/large-file-4gb.txt'
MyIO.new(filename).each{|x| puts x }

Lea sobre esto aquí en esta publicación detallada:

Ruby Magic Slurping & Streaming Files por AppSignal

Khalil Gharbaoui
fuente
Cuidado: ese código ignorará la última línea si no termina con un salto de línea (al menos en Linux).
Jorgen el
Creo que insertar "block.call (@buffer)" antes de "@ io.close" recogerá la línea incompleta que falta. Sin embargo, he jugado con Ruby solo un día, así que podría estar equivocado. Funcionó en mi aplicación :)
Jorgen
Después de leer la publicación de AppSignal, parece que ha habido un pequeño malentendido aquí. El código que copió de esa publicación que hace un IO almacenado es una implementación de ejemplo de lo que Ruby realmente hace con File.foreach o IO.foreach (que son el mismo método). Deben usarse, y no necesita volver a implementarlos de esta manera.
Peter H. Boling
@ PeterH.Boling También estoy por la mentalidad de usar y no reimplementar la mayor parte del tiempo. Pero el rubí nos permite abrir cosas y hurgar en sus entrañas sin vergüenza, es una de sus ventajas. No hay un verdadero "debería" o "no debería" especialmente en rubí / rieles. Siempre que sepa lo que está haciendo y escriba pruebas para ello.
Khalil Gharbaoui
0
content = `cat file`

Creo que este método es el más "poco común". Tal vez sea un poco complicado, pero funciona si catestá instalado.

holaqiu
fuente
1
Un truco útil, pero llamar al shell tiene muchas dificultades, incluyendo 1) los comandos pueden diferir en diferentes sistemas operativos, 2) es posible que deba escapar espacios en el nombre del archivo. Es mucho mejor usar las funciones incorporadas de Ruby, por ejemplocontent = File.read(filename)
Jeff Ward,