Каковы все распространенные способы чтения файлов в Ruby?


280

Каковы все распространенные способы чтения файлов в Ruby?

Например, вот один метод:

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

Я знаю, что Руби чрезвычайно гибок. Каковы преимущества / недостатки каждого подхода?


6
Я не думаю, что текущий победный ответ правильный.
Звоните

Ответы:


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

Также возможно явно закрыть файл после того, как описано выше (передать блок, чтобы закрыть openего для вас):

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

14
Это вряд ли идиоматичный рубин. Используйте foreachвместо openи обойтись без each_lineблока.
Жестянщик

7
f.each { |line| ... }и f.each_line { |line| ... }похоже, ведут себя так же (по крайней мере, в Ruby 2.0.0).
chbrown

327

Самый простой способ, если файл не слишком длинный:

puts File.read(file_name)

Действительно, IO.readили File.readавтоматически закрывать файл, поэтому нет необходимости использовать File.openс блоком.


16
IO.readили File.readтакже автоматически закрывать файл, хотя ваша формулировка звучит так, как будто это не так.
Phrogz

15
он уже сказал "если файл не слишком длинный". Подходит для моего случая идеально.
JayP

227

Остерегайтесь «хлебать» файлы. Это когда вы читаете весь файл в память сразу.

Проблема в том, что он плохо масштабируется. Вы можете разрабатывать код с файлом разумного размера, затем запустить его в производство и вдруг обнаружите, что пытаетесь прочитать файлы размером в гигабайты, и ваш хост зависает, когда пытается прочитать и выделить память.

Строковый ввод-вывод очень быстрый и почти всегда эффективен, как прихлебывание. Это удивительно быстро на самом деле.

Мне нравится использовать:

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

или

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

Файл наследуется от IO и foreachнаходится в IO, поэтому вы можете использовать любой из них.

У меня есть некоторые тесты, показывающие влияние попыток чтения больших файлов с помощью readпострочного ввода-вывода в разделе « Почему« выкраивать »файл не является хорошей практикой? ».


6
Это именно то, что я искал. У меня есть файл с пятью миллионами строк, и я действительно не хотел загружать его в память.
Скотти С.

68

Вы можете прочитать файл сразу:

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

Когда файл большой или может быть большим, обычно лучше обрабатывать его построчно:

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

Иногда вы хотите получить доступ к дескриптору файла или контролировать чтение:

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

В случае двоичных файлов вы можете указать nil-разделитель и размер блока, например:

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

Наконец, вы можете сделать это без блока, например, при обработке нескольких файлов одновременно. В этом случае файл должен быть явно закрыт (улучшено согласно комментарию @antinome):

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

Ссылки: Файловый API и IO API .


2
Там нет for_eachв файле или IO. Используйте foreachвместо этого.
Жестянщик

1
Я обычно использую текстовый редактор Sublime Text с плагином RubyMarkers, когда документирую код, который будет использоваться в ответах здесь. Это позволяет легко показать промежуточные результаты, аналогично использованию IRB. Также плагин Seeing Is Believe для Sublime Text 2 действительно мощный.
Жестянщик

1
Отличный ответ. В последнем примере я мог бы предложить использовать whileвместо loopи использовать, ensureчтобы обеспечить закрытие файла, даже если возникает исключение. Вот так (замените точку с запятой на новую строку) begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
Антином

1
да, это намного лучше @antinome, улучшил ответ. Спасибо!
Виктор Клос

26

Один простой метод заключается в использовании readlines:

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

Каждая строка во входном файле будет записана в массиве. Метод обрабатывает открытие и закрытие файла для вас.


5
Как readи любой другой вариант, это приведет к извлечению всего файла в память, что может вызвать серьезные проблемы, если файл больше доступной памяти. Кроме того, поскольку это массив, Ruby должен создать массив, что дополнительно замедляет процесс.
Жестянщик


9

Я обычно делаю это:

open(path_in_string, &:read)

Это даст вам весь текст в виде строкового объекта. Работает только под Ruby 1.9.


Это мило и коротко! Это тоже закрывает файл?
mrgreenfur

5
Он закрывает его, но не масштабируется, поэтому будьте осторожны.
Жестянщик

3

вернуть последние n строк из your_file.log или .txt

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

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

1

Еще более эффективный способ - потоковая передача, запрашивая ядро ​​операционной системы открыть файл, а затем считывать байты из него по крупицам. При чтении файла на строку в Ruby данные берутся из файла по 512 байт за раз и после этого разделяются на «строки».

Буферизуя содержимое файла, количество вызовов ввода / вывода уменьшается при делении файла на логические порции.

Пример:

Добавьте этот класс в ваше приложение в качестве объекта службы:

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

Вызовите его и передайте :eachметод блок:

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

Читайте об этом здесь в этом подробном сообщении:

Ruby Magic Slurping & Streaming файлы от AppSignal


Обратите внимание: этот код будет игнорировать последнюю строку, если он не заканчивается переводом строки (по крайней мере, в Linux).
Йорген

Я думаю, что вставка «block.call (@buffer)» перед «@ io.close» подберет недостающую неполную строку. Тем не менее, я играл с Руби только один день, поэтому я могу ошибаться. Это сработало в моем приложении :)
Йорген

После прочтения поста AppSignal кажется, что здесь произошло небольшое недоразумение. Код, который вы скопировали из этого поста, который выполняет буферизованный ввод-вывод, является примером реализации того, что на самом деле Ruby делает с File.foreach или IO.foreach (это тот же метод). Их следует использовать, и вам не нужно переопределять их вот так.
Питер Х. Болинг

@ PeterH.Boling Я также за менталитет «используй и не переопределяй» большую часть времени. Но рубин позволяет нам открывать вещи и тыкать в их внутренности без стыда, это одна из его привилегий. Здесь нет настоящих «следует» или «не следует», особенно в рубинах / рельсах. Пока ты знаешь, что делаешь, и пишешь тесты для этого.
Халил Гарбауи

0
content = `cat file`

Я думаю, что этот метод является наиболее "необычным". Может быть, это немного сложно, но работает, если catустановлен.


1
Удобный трюк, но обращение к оболочке имеет много подводных камней, в том числе 1) команды могут отличаться в разных ОС, 2) вам может понадобиться экранировать пробелы в имени файла. Вам гораздо лучше использовать встроенные функции Ruby, например,content = File.read(filename)
Джефф Уорд
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.