Quali sono tutti i modi comuni per leggere un file in Ruby?


280

Quali sono tutti i modi comuni per leggere un file in Ruby?

Ad esempio, ecco un metodo:

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

So che Ruby è estremamente flessibile. Quali sono i vantaggi / gli svantaggi di ciascun approccio?


6
Non penso che l'attuale risposta vincente sia corretta.
inger

Risposte:


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

È anche possibile chiudere esplicitamente il file dopo come sopra (passare un blocco per chiuderlo openper te):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close

14
Questo è Ruby appena idiomatico. Utilizzare foreachinvece di opene dispensare con il each_lineblocco.
Tin Man,

7
f.each { |line| ... }e f.each_line { |line| ... }sembrano avere lo stesso comportamento (almeno in Ruby 2.0.0).
chbrown,

327

Il modo più semplice se il file non è troppo lungo è:

puts File.read(file_name)

Anzi, IO.reado File.readchiudi automaticamente il file, quindi non è necessario utilizzarlo File.opencon un blocco.


16
IO.reado File.readanche chiudere automaticamente il file, anche se la tua formulazione sembra che non lo facciano.
Phrogz,

15
ha già detto "se il file non è troppo lungo". Si adatta perfettamente al mio caso.
jayP

227

Diffidare di file "slurping". Questo è quando leggi l'intero file in memoria contemporaneamente.

Il problema è che non si adatta bene. Potresti sviluppare codice con un file di dimensioni ragionevoli, quindi metterlo in produzione e improvvisamente scopri che stai provando a leggere i file misurando in gigabyte e il tuo host si blocca mentre tenta di leggere e allocare memoria.

L'I / O riga per riga è molto veloce e quasi sempre efficace come lo slurping. In realtà è sorprendentemente veloce.

Mi piace usare:

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

o

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

Il file eredita da IO ed foreachè in IO, quindi puoi usare entrambi.

Ho alcuni benchmark che mostrano l'impatto del tentativo di leggere file di grandi dimensioni tramite readI / O vs line-by-line in " Perché" slurping "un file non è una buona pratica? ".


6
Questo e 'esattamente quello che stavo cercando. Ho un file con cinque milioni di righe e non volevo davvero che fosse caricato in memoria.
Scotty C.,

68

Puoi leggere il file tutto in una volta:

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

Quando il file è grande o può essere grande, di solito è meglio elaborarlo riga per riga:

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

A volte vuoi accedere all'handle del file o controllare tu stesso le letture:

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

Nel caso di file binari, è possibile specificare un separatore zero e una dimensione di blocco, in questo modo:

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

Finalmente puoi farlo senza un blocco, ad esempio quando elabori più file contemporaneamente. In tal caso il file deve essere esplicitamente chiuso (migliorato come da commento di @antinome):

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

Riferimenti: File API e IO API .


2
Non c'è for_eachin File o IO. Usa foreachinvece.
Tin Man,

1
Di solito uso l'editor di testo sublime, con il plug-in RubyMarkers, quando documento il codice da utilizzare nelle risposte qui. Rende davvero facile mostrare risultati intermedi, simile all'utilizzo dell'IRB. Anche il plug-in Seeing Is Believing per Sublime Text 2 è davvero potente.
Tin Man,

1
Bella risposta. Per l'ultimo esempio, potrei suggerire di usare whileinvece di loope usando ensureper assicurarsi che il file venga chiuso anche se viene sollevata un'eccezione. Ti piace questa (sostituire punti e virgola con a capo): begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome,

1
sì, è molto meglio @antinome, migliorata la risposta. Grazie!
Victor Klos,

26

Un metodo semplice è utilizzare readlines:

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

Ogni riga nel file di input sarà una voce nell'array. Il metodo gestisce l'apertura e la chiusura del file per te.


5
Come con readqualsiasi altra variante, questo rimuoverà l'intero file in memoria, il che può causare gravi problemi se il file è più grande della memoria disponibile. Inoltre, poiché è un array, Ruby deve creare l'array, rallentando ulteriormente il processo.
Tin Man,


9

Di solito lo faccio:

open(path_in_string, &:read)

Questo ti darà l'intero testo come oggetto stringa. Funziona solo con Ruby 1.9.


Questo è carino e breve! Chiude anche il file?
mrgreenfur,

5
Lo chiude, ma non è scalabile, quindi fai attenzione.
Tin Man,

3

restituisce le ultime n righe da your_file.log o .txt

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

last_100_lines = `tail -n 100 #{path}`

1

Un modo ancora più efficiente è lo streaming chiedendo al kernel del sistema operativo di aprire un file, quindi leggere byte da esso bit per bit. Quando si legge un file per riga in Ruby, i dati vengono presi dal file 512 byte alla volta e successivamente suddivisi in "righe".

Bufferando il contenuto del file, il numero di chiamate I / O viene ridotto mentre si divide il file in blocchi logici.

Esempio:

Aggiungi questa classe alla tua app come oggetto di servizio:

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

Chiamalo e passa il :eachmetodo a blocco:

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

Leggi qui in questo post dettagliato:

Ruby Magic: Slurping & Streaming Files di AppSignal


Attenzione: quel codice ignorerà l'ultima riga se non termina con un avanzamento riga (almeno in Linux).
Jorgen,

Penso che l'inserimento di "block.call (@buffer)" prima di "@ io.close" rileverà la riga incompleta mancante. Tuttavia, ho suonato con Ruby solo un giorno, quindi potrei sbagliarmi. Ha funzionato nella mia applicazione :)
Jorgen il

Dopo aver letto il post di AppSignal sembra che ci sia stato un piccolo malinteso qui. Il codice che hai copiato da quel post che esegue un IO bufferizzato è un'implementazione di esempio di ciò che Ruby fa effettivamente con File.foreach o IO.foreach (che sono lo stesso metodo). Dovrebbero essere usati e non è necessario reimplementarli in questo modo.
Peter H. Boling,

@ PeterH.Boling Sono anche per la mentalità usa e non reimplementare il più delle volte. Ma il rubino ci permette di aprire le cose e colpire le loro viscere senza vergogna, è uno dei suoi vantaggi. Non esiste un vero "dovrebbe" o "non dovrebbe" soprattutto nei rubini / rotaie. Finché sai cosa stai facendo e scrivi dei test per questo.
Khalil Gharbaoui,

0
content = `cat file`

Penso che questo metodo sia il più "raro". Forse è un po 'complicato, ma funziona se catè installato.


1
Un trucco utile, ma chiamare la shell ha molte insidie, tra cui 1) i comandi possono differire su diversi sistemi operativi, 2) potrebbe essere necessario sfuggire agli spazi nel nome del file. Stai molto meglio utilizzando le funzioni integrate di Ruby, ad escontent = File.read(filename)
Jeff Ward,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.