Каковы все распространенные способы чтения файлов в 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.