Tất cả những cách phổ biến để đọc một tệp trong Ruby là gì?


280

Tất cả những cách phổ biến để đọc một tệp trong Ruby là gì?

Ví dụ, đây là một phương thức:

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

Tôi biết Ruby cực kỳ linh hoạt. Những lợi ích / hạn chế của mỗi phương pháp là gì?


6
Tôi không nghĩ rằng câu trả lời chiến thắng hiện tại là chính xác.
nhập

Câu trả lời:


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

Cũng có thể đóng tệp một cách rõ ràng sau như trên (chuyển một khối để openđóng tệp cho bạn):

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

14
Đây không phải là thành ngữ Ruby. Sử dụng foreachthay vì openvà phân phối với các each_linekhối.
Tin Man

7
f.each { |line| ... }f.each_line { |line| ... }dường như có hành vi tương tự (ít nhất là trong Ruby 2.0.0).
chbrown

327

Cách dễ nhất nếu tệp không quá dài là:

puts File.read(file_name)

Thật vậy, IO.readhoặc File.readtự động đóng tệp, do đó không cần sử dụng File.openvới một khối.


16
IO.readhoặc File.readcũng tự động đóng tệp, mặc dù cách diễn đạt của bạn nghe có vẻ như không.
Phrogz

15
anh ấy đã nói "nếu tập tin không quá dài". Phù hợp với trường hợp của tôi một cách hoàn hảo.
jayP

227

Hãy cảnh giác với các tập tin "nhếch nhác". Đó là khi bạn đọc toàn bộ tập tin vào bộ nhớ cùng một lúc.

Vấn đề là nó không có quy mô tốt. Bạn có thể đang phát triển mã với một tệp có kích thước hợp lý, sau đó đưa nó vào sản xuất và đột nhiên thấy bạn đang cố đọc các tệp có dung lượng gigabyte và máy chủ của bạn đang đóng băng khi nó cố đọc và cấp phát bộ nhớ.

I / O line-by-line rất nhanh, và hầu như luôn hiệu quả như slurping. Thật đáng ngạc nhiên nhanh chóng.

Tôi thích sử dụng:

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

hoặc là

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

Tệp kế thừa từ IO và foreachnằm trong IO, vì vậy bạn có thể sử dụng một trong hai.

Tôi có một số điểm chuẩn cho thấy tác động của việc cố gắng đọc các tệp lớn thông qua readI / O so với từng dòng tại " Tại sao" nhét "một tệp không phải là một thực tiễn tốt? ".


6
Điều này thật đúng với gì mà tôi đã tìm kiếm. Tôi đã có một tệp có năm triệu dòng và thực sự không muốn nó được tải vào bộ nhớ.
Scotty C.

68

Bạn có thể đọc tất cả các tập tin cùng một lúc:

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

Khi tệp lớn hoặc có thể lớn, thường sẽ tốt hơn khi xử lý từng dòng một:

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

Đôi khi bạn muốn truy cập vào tập tin xử lý mặc dù hoặc tự kiểm soát việc đọc:

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

Trong trường hợp tệp nhị phân, bạn có thể chỉ định dải phân cách và kích thước khối, như vậy:

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

Cuối cùng, bạn có thể làm điều đó mà không cần một khối, ví dụ như khi xử lý nhiều tệp cùng một lúc. Trong trường hợp đó, tệp phải được đóng rõ ràng (được cải thiện theo nhận xét của @antinome):

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

Tài liệu tham khảo: API tệpAPI IO .


2
Không có for_eachtrong Tệp hoặc IO. Sử dụng foreachthay thế.
Tin Man

1
Tôi thường sử dụng trình soạn thảo Sublime Text, với plugin RubyMarkers, khi viết tài liệu mã được sử dụng trong các câu trả lời ở đây. Nó làm cho nó thực sự dễ dàng để hiển thị kết quả trung gian, tương tự như sử dụng IRB. Ngoài ra, plugin Seeing Is Bel Bel cho Sublime Text 2 thực sự rất mạnh mẽ.
Tin Man

1
Câu trả lời chính xác. Đối với ví dụ cuối tôi có thể đề nghị sử dụng whilethay vì loopvà sử dụng ensuređể đảm bảo tệp được đóng ngay cả khi có ngoại lệ được đưa ra. Như thế này (thay thế dấu chấm phẩy bằng dòng mới) : begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome

1
vâng, đó là tốt hơn nhiều @antinome, đã cải thiện câu trả lời. cảm ơn!
Victor Klos

26

Một phương pháp đơn giản là sử dụng readlines:

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

Mỗi dòng trong tệp đầu vào sẽ là một mục trong mảng. Phương pháp xử lý mở và đóng tệp cho bạn.


5
Như với readhoặc bất kỳ biến thể nào, điều này sẽ kéo toàn bộ tệp vào bộ nhớ, điều này có thể gây ra vấn đề lớn nếu tệp lớn hơn bộ nhớ khả dụng. Ngoài ra, vì là một mảng, Ruby phải tạo ra mảng, làm chậm quá trình bổ sung.
Tin Man


9

Tôi thường làm điều này:

open(path_in_string, &:read)

Điều này sẽ cung cấp cho bạn toàn bộ văn bản dưới dạng một đối tượng chuỗi. Nó chỉ hoạt động dưới Ruby 1.9.


Điều này là tốt đẹp và ngắn! Nó cũng đóng tập tin?
mrgreenfur

5
Nó đóng nó, nhưng nó không thể mở rộng nên hãy cẩn thận.
Tin Man

3

trả về n dòng cuối cùng từ your_file.log hoặc .txt

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

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

1

Một cách thậm chí hiệu quả hơn là phát trực tuyến bằng cách yêu cầu kernel của hệ điều hành mở tệp, sau đó đọc từng byte từ nó. Khi đọc một tệp trên mỗi dòng trong Ruby, dữ liệu được lấy từ tệp 512 byte tại một thời điểm và được tách ra trong các dòng của dòng Sau đó.

Bằng cách đệm nội dung của tệp, số lượng cuộc gọi I / O sẽ giảm trong khi chia tệp thành các khối logic.

Thí dụ:

Thêm lớp này vào ứng dụng của bạn dưới dạng đối tượng dịch vụ:

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

Gọi nó và truyền :eachphương thức một khối:

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

Đọc về nó ở đây trong bài chi tiết này:

Ruby Magic Slurping & streaming tập tin bởi AppSignal


Xem ra: mã đó sẽ bỏ qua dòng cuối cùng nếu nó không kết thúc bằng một nguồn cấp dữ liệu (ít nhất là trong Linux).
Jorgen

Tôi nghĩ rằng việc chèn "block.call (@buffer)" trước "@ io.c Đóng" sẽ chọn dòng không hoàn chỉnh bị thiếu. Tuy nhiên, tôi đã chơi với Ruby chỉ một ngày nên tôi cũng có thể sai. Nó hoạt động trong ứng dụng của tôi :)
Jorgen

Sau khi đọc bài đăng trên AppSignal, dường như đã có một sự hiểu lầm nhỏ ở đây. Mã bạn đã sao chép từ bài đăng đó có IO được đệm là một ví dụ triển khai những gì Ruby thực sự làm với File.foreach hoặc IO.foreach (cùng một phương thức). Chúng nên được sử dụng và bạn không cần phải thực hiện lại chúng như thế này.
Peter H. Boling

@ PeterH.Boling Tôi cũng dành cho tâm lý sử dụng và không thực hiện lại hầu hết thời gian. Nhưng ruby ​​không cho phép chúng ta mở mọi thứ và chọc vào bên trong của chúng mà không xấu hổ, đó là một trong những đặc quyền. Không có 'nên' hoặc 'không nên' đặc biệt là trong ruby ​​/ rails. Miễn là bạn biết những gì bạn đang làm, và bạn viết bài kiểm tra cho nó.
Khalil Gharbaoui

0
content = `cat file`

Tôi nghĩ rằng phương pháp này là phương pháp "không phổ biến" nhất. Có thể nó là loại khó khăn, nhưng nó hoạt động nếu catđược cài đặt.


1
Một mẹo nhỏ hữu ích, nhưng việc gọi ra shell có rất nhiều cạm bẫy, bao gồm 1) các lệnh có thể khác nhau trên các hệ điều hành khác nhau, 2) bạn có thể cần phải thoát khoảng trắng trong tên tệp. Bạn tốt hơn bằng cách sử dụng của Ruby built-in chức năng, ví dụcontent = File.read(filename)
Jeff Phường
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.