Apa saja cara umum untuk membaca file di Ruby?

280

Apa saja cara umum untuk membaca file di Ruby?

Sebagai contoh, berikut adalah satu metode:

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

Saya tahu Ruby sangat fleksibel. Apa manfaat / kelemahan dari setiap pendekatan?

dsg
sumber
6
Saya tidak berpikir jawaban kemenangan saat ini benar.
inger

Jawaban:

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

Dimungkinkan juga untuk secara eksplisit menutup file setelah seperti di atas (lulus blok untuk openmenutupnya untuk Anda):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close
fl00r
sumber
14
Ini bukan Ruby yang idiomatis. Gunakan foreachalih-alih opendan buang each_lineblokir.
the Tin Man
7
f.each { |line| ... }dan f.each_line { |line| ... }tampaknya memiliki perilaku yang sama (setidaknya di Ruby 2.0.0).
chbrown
327

Cara termudah jika file tidak terlalu panjang adalah:

puts File.read(file_name)

Memang, IO.readatau File.readsecara otomatis menutup file, sehingga tidak perlu menggunakan File.openblok.

mckeed
sumber
16
IO.readatau File.readjuga secara otomatis menutup file, meskipun kata-kata Anda membuatnya terdengar seperti tidak.
Phrogz
15
dia sudah mengatakan "jika file tidak terlalu panjang". Cocok dengan kasus saya dengan sempurna.
jayP
227

Berhati-hatilah dengan file "slurping". Saat itulah Anda membaca seluruh file ke memori sekaligus.

Masalahnya adalah itu tidak skala dengan baik. Anda bisa mengembangkan kode dengan file berukuran wajar, lalu memproduksinya dan tiba-tiba menemukan Anda mencoba membaca file berukuran dalam gigabytes, dan host Anda membeku ketika mencoba membaca dan mengalokasikan memori.

Baris-demi-baris I / O sangat cepat, dan hampir selalu seefektif slurping. Ini ternyata sangat cepat.

Saya suka menggunakan:

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

atau

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

File mewarisi dari IO, dan foreachada di IO, jadi Anda bisa menggunakan keduanya.

Saya memiliki beberapa tolok ukur yang menunjukkan dampak dari mencoba membaca file besar melalui readvs. I-O baris demi baris di " Mengapa" menyeruput "file bukan praktik yang baik? ".

the Tin Man
sumber
6
Ini persis apa yang saya cari. Saya punya file dengan lima juta baris, dan benar-benar tidak ingin itu dimuat ke dalam memori.
Scotty C.
68

Anda dapat membaca file sekaligus:

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

Ketika file besar, atau mungkin besar, biasanya lebih baik untuk memprosesnya baris demi baris:

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

Kadang-kadang Anda ingin akses ke penanganan file atau mengontrol bacaan sendiri:

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

Dalam hal file biner, Anda dapat menentukan nil-separator dan ukuran blok, seperti:

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

Akhirnya Anda bisa melakukannya tanpa blok, misalnya saat memproses beberapa file secara bersamaan. Dalam hal ini file tersebut harus ditutup secara eksplisit (ditingkatkan sesuai komentar @antinome):

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

Referensi: File API dan IO API .

Victor Klos
sumber
2
Tidak ada for_eachdalam File atau IO. Gunakan foreachsebagai gantinya.
the Tin Man
1
Saya biasanya menggunakan editor Sublime Text, dengan plugin RubyMarkers, ketika mendokumentasikan kode yang akan digunakan dalam jawaban di sini. Itu membuatnya sangat mudah untuk menunjukkan hasil antara, mirip dengan menggunakan IRB. Juga plugin Seeing Is Believing untuk Sublime Text 2 sangat kuat.
the Tin Man
1
Jawaban yang bagus Untuk contoh terakhir saya mungkin menyarankan menggunakan whiledaripada loopmenggunakan dan ensureuntuk memastikan file ditutup bahkan jika pengecualian dinaikkan. Seperti ini (ganti semi-titik dua dengan baris baru): begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome
1
ya itu jauh lebih baik @antinome, meningkatkan jawabannya. Terima kasih!
Victor Klos
26

Salah satu metode sederhana adalah dengan menggunakan readlines:

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

Setiap baris dalam file input akan menjadi entri dalam array. Metode ini menangani membuka dan menutup file untuk Anda.

bta
sumber
5
Seperti readhalnya varian apa pun, ini akan menarik seluruh file ke dalam memori, yang dapat menyebabkan masalah besar jika file lebih besar dari memori yang tersedia. Selain itu, karena merupakan array, Ruby harus membuat array, memperlambat prosesnya juga.
the Tin Man
9

Saya biasanya melakukan ini:

open(path_in_string, &:read)

Ini akan memberi Anda seluruh teks sebagai objek string. Ini hanya berfungsi di bawah Ruby 1.9.

sawa
sumber
Ini bagus dan pendek! Apakah itu menutup file juga?
mrgreenfur
5
Itu menutupnya, tapi itu tidak bisa diukur jadi hati-hati.
the Tin Man
3

kembalikan n baris terakhir dari your_file.log atau .txt

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

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

Cara yang lebih efisien adalah streaming dengan meminta kernel sistem operasi untuk membuka file, kemudian membaca byte dari file itu sedikit demi sedikit. Saat membaca file per baris di Ruby, data diambil dari file 512 byte sekaligus dan dibagi menjadi "baris" setelah itu.

Dengan buffering konten file, jumlah panggilan I / O berkurang saat membagi file dalam potongan logis.

Contoh:

Tambahkan kelas ini ke aplikasi Anda sebagai objek layanan:

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

Sebut saja dan berikan :eachmetode ini blok:

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

Baca di sini di pos terperinci ini:

Ruby Magic Slurping & Streaming File Menurut AppSignal

Khalil Gharbaoui
sumber
Awas: kode itu akan mengabaikan baris terakhir jika tidak diakhiri dengan linefeed (paling tidak di Linux).
Jorgen
Saya pikir memasukkan "block.call (@buffer)" sebelum "@ io.close" akan mengambil baris yang tidak lengkap yang hilang. Namun, saya hanya bermain dengan Ruby satu hari sehingga saya bisa saja salah. Itu bekerja di aplikasi saya :)
Jorgen
Setelah membaca posting AppSignal, tampaknya ada sedikit kesalahpahaman di sini. Kode yang Anda salin dari pos yang melakukan buffer IO adalah contoh implementasi dari apa yang sebenarnya dilakukan Ruby dengan File.foreach, atau IO.foreach (yang merupakan metode yang sama). Mereka harus digunakan, dan Anda tidak perlu mengimplementasikannya seperti ini.
Peter H. Boling
@ PeterH.Boling Saya juga sering menggunakan mentalitas use-and-don't-reimplement. Tapi ruby ​​memang memungkinkan kita untuk membuka barang-barang dan menyodok isi perut mereka tanpa rasa malu itu salah satu keistimewaannya. Tidak ada 'seharusnya' atau 'tidak boleh' terutama di ruby ​​/ rails. Selama Anda tahu apa yang Anda lakukan, dan Anda menulis tes untuk itu.
Khalil Gharbaoui
0
content = `cat file`

Saya pikir metode ini adalah yang paling "tidak umum". Mungkin agak sulit, tetapi berfungsi jika catdiinstal.

haloqiu
sumber
1
Trik yang praktis, tetapi memanggil shell memiliki banyak jebakan, termasuk 1) perintah mungkin berbeda pada OS yang berbeda, 2) Anda mungkin perlu melarikan diri ruang dalam nama file. Anda jauh lebih baik menggunakan fungsi content = File.read(filename)
Jeff Ward