Was sind die gängigen Methoden zum Lesen einer Datei in Ruby?

280

Was sind die gängigen Methoden zum Lesen einer Datei in Ruby?

Hier ist zum Beispiel eine Methode:

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

Ich weiß, dass Ruby extrem flexibel ist. Was sind die Vor- und Nachteile jedes Ansatzes?

dsg
quelle
6
Ich denke nicht, dass die aktuelle Gewinnerantwort richtig ist.
Inger

Antworten:

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

Es ist auch möglich, die Datei nach wie oben explizit zu schließen (übergeben Sie einen Block, um opensie für Sie zu schließen):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close
fl00r
quelle
14
Dies ist kaum idiomatisch Ruby. Verwenden Sie foreachanstelle des Blocks openund verzichten Sie darauf each_line.
der Blechmann
7
f.each { |line| ... }und f.each_line { |line| ... }scheinen das gleiche Verhalten zu haben (zumindest in Ruby 2.0.0).
Chbrown
327

Der einfachste Weg, wenn die Datei nicht zu lang ist, ist:

puts File.read(file_name)

In der Tat IO.readoder File.readschließen Sie die Datei automatisch, sodass Sie sie nicht File.openmit einem Block verwenden müssen.

mckeed
quelle
16
IO.readoder File.readschließen Sie die Datei auch automatisch, obwohl Ihr Wortlaut so klingt, als ob dies nicht der Fall wäre.
Phrogz
15
er sagte bereits "wenn die Datei nicht zu lang ist". Passt perfekt zu meinem Fall.
JayP
227

Seien Sie vorsichtig beim "Schlürfen" von Dateien. Dann lesen Sie die gesamte Datei auf einmal in den Speicher.

Das Problem ist, dass es nicht gut skaliert. Möglicherweise entwickeln Sie Code mit einer Datei mit angemessener Größe, stellen ihn dann in Produktion und stellen plötzlich fest, dass Sie versuchen, Dateien mit einer Größe von Gigabyte zu lesen, und Ihr Host friert ein, während er versucht, Speicher zu lesen und zuzuweisen.

Zeile für Zeile ist die E / A sehr schnell und fast immer so effektiv wie das Schlürfen. Es ist eigentlich überraschend schnell.

Ich benutze gerne:

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

oder

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

Die Datei erbt von IO und foreachbefindet sich in IO, sodass Sie beide verwenden können.

Ich habe einige Benchmarks, die zeigen, wie sich der Versuch auswirkt, große Dateien über readzeilenweise E / A zu lesen, unter " Warum ist das" Schlürfen "einer Datei keine gute Vorgehensweise? ".

den Blechmann
quelle
6
Genau das habe ich gesucht. Ich habe eine Datei mit fünf Millionen Zeilen und wollte wirklich nicht, dass diese in den Speicher geladen wird.
Scotty C.
68

Sie können die Datei auf einmal lesen:

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

Wenn die Datei groß ist oder groß sein kann, ist es normalerweise besser, sie zeilenweise zu verarbeiten:

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

Manchmal möchten Sie jedoch auf das Dateihandle zugreifen oder die Lesevorgänge selbst steuern:

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

Bei Binärdateien können Sie ein Nulltrennzeichen und eine Blockgröße wie folgt angeben:

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

Schließlich können Sie es ohne Block tun, beispielsweise wenn Sie mehrere Dateien gleichzeitig verarbeiten. In diesem Fall muss die Datei explizit geschlossen werden (verbessert gemäß Kommentar von @antinome):

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

Referenzen: Datei-API und E / A-API .

Victor Klos
quelle
2
Es gibt keine for_eachin Datei oder E / A. Verwenden Sie foreachstattdessen.
der Blechmann
1
Normalerweise verwende ich den Sublime Text-Editor mit dem RubyMarkers-Plugin, wenn ich Code dokumentiere, der in den Antworten hier verwendet werden soll. Es macht es wirklich einfach, Zwischenergebnisse anzuzeigen, ähnlich wie bei der Verwendung von IRB. Auch das Plugin "Sehen ist Glauben" für Sublime Text 2 ist wirklich mächtig.
Der Blechmann
1
Gute Antwort. Für das letzte Beispiel könnte ich vorschlagen, whileanstelle von loopund zu verwenden, ensureum sicherzustellen, dass die Datei geschlossen wird, auch wenn eine Ausnahme ausgelöst wird. So (Semikolons durch Zeilenumbrüche ersetzen) : begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
Antinom
1
Ja, das ist viel besser @antinome, verbesserte die Antwort. Vielen Dank!
Victor Klos
26

Eine einfache Methode ist zu verwenden readlines:

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

Jede Zeile in der Eingabedatei ist ein Eintrag im Array. Die Methode behandelt das Öffnen und Schließen der Datei für Sie.

bta
quelle
5
Wie bei readoder jeder anderen Variante wird dadurch die gesamte Datei in den Speicher gezogen, was zu großen Problemen führen kann, wenn die Datei größer als der verfügbare Speicher ist. Da es sich um ein Array handelt, muss Ruby das Array erstellen, was den Prozess zusätzlich verlangsamt.
der Blechmann
9

Normalerweise mache ich das:

open(path_in_string, &:read)

Dadurch erhalten Sie den gesamten Text als Zeichenfolgenobjekt. Es funktioniert nur unter Ruby 1.9.

sawa
quelle
Das ist schön kurz! Schließt es auch die Datei?
Mrgreenfur
5
Es schließt es, ist aber nicht skalierbar. Seien Sie also vorsichtig.
der Blechmann
3

Geben Sie die letzten n Zeilen aus your_file.log oder .txt zurück

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

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

Eine noch effizientere Methode ist das Streaming, indem der Kernel des Betriebssystems aufgefordert wird, eine Datei zu öffnen und dann nach und nach Bytes daraus zu lesen. Beim Lesen einer Datei pro Zeile in Ruby werden Daten jeweils 512 Byte aus der Datei entnommen und danach in „Zeilen“ aufgeteilt.

Durch Puffern des Dateiinhalts wird die Anzahl der E / A-Aufrufe reduziert, während die Datei in logische Blöcke aufgeteilt wird.

Beispiel:

Fügen Sie diese Klasse Ihrer App als Serviceobjekt hinzu:

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

Rufen Sie es auf und übergeben Sie der :eachMethode einen Block:

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

Lesen Sie hier in diesem ausführlichen Beitrag darüber:

Ruby Magic Slurping & Streaming-Dateien von AppSignal

Khalil Gharbaoui
quelle
Achtung: Dieser Code ignoriert die letzte Zeile, wenn er nicht mit einem Zeilenvorschub endet (zumindest unter Linux).
Jorgen
Ich denke, das Einfügen von "block.call (@buffer)" vor "@ io.close" wird die fehlende unvollständige Zeile auffangen. Ich habe jedoch nur einen Tag mit Ruby gespielt, also könnte ich mich irren. Es hat in meiner Bewerbung funktioniert :)
Jorgen
Nach dem Lesen des AppSignal-Beitrags scheint es hier ein kleines Missverständnis gegeben zu haben. Der Code, den Sie aus diesem Beitrag kopiert haben, der eine gepufferte E / A ausführt, ist eine Beispielimplementierung dessen, was Ruby tatsächlich mit File.foreach oder IO.foreach (die dieselbe Methode sind) macht. Sie sollten verwendet werden, und Sie müssen sie nicht so neu implementieren.
Peter H. Boling
@ PeterH.Boling Ich bin auch die meiste Zeit für die Mentalität des Gebrauchs und der Nichtimplementierung. Aber Ruby erlaubt es uns, Dinge zu öffnen und in ihr Inneres zu stochern, ohne sich zu schämen, es ist einer seiner Vorteile. Es gibt kein echtes "sollte" oder "sollte nicht", insbesondere bei Rubinen / Schienen. Solange Sie wissen, was Sie tun, und Sie Tests dafür schreiben.
Khalil Gharbaoui
0
content = `cat file`

Ich denke, diese Methode ist die "ungewöhnlichste". Vielleicht ist es etwas schwierig, aber es funktioniert, wenn cates installiert ist.

halloqiu
quelle
1
Ein praktischer Trick, aber das Aufrufen der Shell birgt viele Fallstricke, darunter 1) die Befehle können auf verschiedenen Betriebssystemen unterschiedlich sein, 2) Sie müssen möglicherweise Leerzeichen im Dateinamen maskieren. Sie sind viel besser aus Rubin mit integrierten Funktionen, wie zBcontent = File.read(filename)
Jeff Ward