Jakie są typowe sposoby odczytu pliku w Rubim?


280

Jakie są typowe sposoby odczytu pliku w Rubim?

Na przykład, oto jedna metoda:

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

Wiem, że Ruby jest niezwykle elastyczna. Jakie są zalety / wady każdego podejścia?


6
Nie sądzę, aby obecna zwycięska odpowiedź była poprawna.
inger

Odpowiedzi:


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

Możliwe jest również jawne zamknięcie pliku po jak wyżej (przekaż blok, aby opengo zamknąć):

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

14
To nie jest idiomatyczny Ruby. Użyj foreachzamiast openi zrezygnuj z each_linebloku.
Tin Man

7
f.each { |line| ... }i f.each_line { |line| ... }wydają się mieć takie samo zachowanie (przynajmniej w Ruby 2.0.0).
chbrown

327

Najłatwiejszym sposobem, jeśli plik nie jest zbyt długi, jest:

puts File.read(file_name)

Rzeczywiście IO.readlub File.readautomatycznie zamknij plik, więc nie ma potrzeby używania File.openz blokiem.


16
IO.readlub File.readteż automatycznie zamknij plik, chociaż twoje sformułowania sprawiają, że brzmi on inaczej.
Phrogz,

15
powiedział już „jeśli plik nie jest zbyt długi”. Idealnie pasuje do mojej skrzynki.
jayP,

227

Uważaj na pliki „slurping”. Wtedy od razu odczytujesz cały plik do pamięci.

Problem polega na tym, że nie skaluje się dobrze. Możesz opracowywać kod z plikiem o rozsądnych rozmiarach, a następnie wprowadzić go do produkcji i nagle odkryć, że próbujesz odczytać pliki mierzone w gigabajtach, a Twój host zawiesza się, gdy próbuje odczytać i przydzielić pamięć.

Line-by-I / O jest bardzo szybki i prawie zawsze tak samo skuteczny jak slurping. W rzeczywistości jest zaskakująco szybki.

Lubię używać:

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

lub

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

Plik dziedziczy z IO i foreachjest w IO, więc możesz użyć jednego z nich.

Mam pewne testy porównawcze pokazujące wpływ próby odczytu dużych plików za pomocą operacji readwe / wy wiersz po wierszu w sekcji „ Dlaczego„ zamazywanie ”pliku nie jest dobrą praktyką? ”.


6
Właśnie tego szukałem. Mam plik z pięcioma milionami wierszy i naprawdę nie chciałem, żeby to zostało załadowane do pamięci.
Scotty C.

68

Możesz odczytać plik naraz:

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

Gdy plik jest duży lub może być duży, zwykle lepiej jest go przetwarzać wiersz po wierszu:

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

Czasami chcesz uzyskać dostęp do uchwytu pliku lub samodzielnie kontrolować odczyty:

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

W przypadku plików binarnych możesz podać separator zerowy i rozmiar bloku, na przykład:

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

Wreszcie możesz to zrobić bez bloku, na przykład podczas przetwarzania wielu plików jednocześnie. W takim przypadku plik musi zostać jawnie zamknięty (poprawiony zgodnie z komentarzem @antinome):

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

Odnośniki: File API i IO API .


2
Nie ma for_eachw pliku lub we / wy. Użyj foreachzamiast tego.
Tin Man

1
Zwykle używam edytora Sublime Text z wtyczką RubyMarkers, kiedy dokumentuję kod, który ma być użyty w odpowiedziach tutaj. Ułatwia to wyświetlanie wyników pośrednich, podobnie jak w przypadku IRB. Również wtyczka Seeing Is Believing do Sublime Text 2 jest naprawdę potężna.
Tin Man

1
Świetna odpowiedź. W ostatnim przykładzie mogę zasugerować użycie whilezamiast loopi użycie, ensureaby upewnić się, że plik zostanie zamknięty, nawet jeśli zostanie zgłoszony wyjątek. Tak (zastąpienia średników z nowej linii) begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome

1
tak, to jest znacznie lepsze @antinome, poprawiłem odpowiedź. dzięki!
Victor Klos,

26

Jedną z prostych metod jest użycie readlines:

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

Każda linia w pliku wejściowym będzie wpisem w tablicy. Ta metoda obsługuje otwieranie i zamykanie pliku.


5
Podobnie jak w przypadku readdowolnego wariantu spowoduje to wciągnięcie całego pliku do pamięci, co może powodować poważne problemy, jeśli plik jest większy niż dostępna pamięć. Ponadto, ponieważ jest to tablica, Ruby musi ją utworzyć, co dodatkowo spowalnia proces.
Tin Man


9

Zazwyczaj robię to:

open(path_in_string, &:read)

To da ci cały tekst jako ciąg znaków. Działa tylko pod Ruby 1.9.


To jest miłe i krótkie! Czy to też zamyka plik?
mrgreenfur

5
Zamyka go, ale nie jest skalowalny, więc bądź ostrożny.
Tin Man

3

zwraca ostatnie n wierszy z twojego_pliku.log lub .txt

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

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

1

Jeszcze bardziej wydajnym sposobem jest przesyłanie strumieniowe, prosząc jądro systemu operacyjnego o otwarcie pliku, a następnie odczytanie z niego bajtów po kawałku. Podczas odczytywania pliku w wierszu w języku Ruby dane są pobierane z pliku 512 bajtów naraz i dzielone na „linie”.

Dzięki buforowaniu zawartości pliku zmniejsza się liczba wywołań We / Wy, dzieląc plik na logiczne części.

Przykład:

Dodaj tę klasę do swojej aplikacji jako obiekt usługi:

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

Zadzwoń i przekaż :eachmetodzie blok:

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

Przeczytaj o tym tutaj w tym szczegółowym poście:

Ruby Magic Slurping & Streaming Files By AppSignal


Uwaga: ten kod zignoruje ostatnią linię, jeśli nie kończy się na linii (przynajmniej w Linuksie).
Jorgen

Myślę, że wstawienie „block.call (@buffer)” przed „@ io.close” odbierze brakującą niekompletną linię. Jednak grałem z Ruby tylko jeden dzień, więc mogłem się mylić. Działa w mojej aplikacji :)
Jorgen

Po przeczytaniu postu AppSignal wydaje się, że nastąpiło tutaj małe nieporozumienie. Kod skopiowany z tego postu, który wykonuje buforowane operacje we / wy, jest przykładową implementacją tego, co Ruby faktycznie robi z File.foreach lub IO.foreach (które są tą samą metodą). Powinny być używane i nie trzeba ich ponownie wprowadzać w ten sposób.
Peter H. Boling

@ PeterH.Boling Jestem również za mentalnością używania i nie reimplementacji przez większość czasu. Ale rubin pozwala nam otwierać rzeczy i szturchać ich wnętrze bez wstydu, to jedna z jego zalet. Nie ma prawdziwego „powinien” lub „nie powinien”, szczególnie w rubinach / szynach. Tak długo, jak wiesz, co robisz, i piszesz na to testy.
Khalil Gharbaoui

0
content = `cat file`

Myślę, że ta metoda jest najbardziej „rzadka”. Może to trochę trudne, ale działa, jeśli catjest zainstalowane.


1
Przydatna sztuczka, ale wzywanie do powłoki ma wiele pułapek, w tym 1) polecenia mogą się różnić w różnych systemach operacyjnych, 2) może być konieczne wstawienie spacji w nazwie pliku. Jesteś o wiele lepiej wyłączyć za pomocą Ruby wbudowanych funkcji, na przykładcontent = File.read(filename)
Jeff Ward
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.