Читайте, редактируйте и пишите текстовый файл построчно, используя Ruby - PullRequest
48 голосов
/ 09 декабря 2010

Есть ли хороший способ читать, редактировать и записывать файлы на месте в Ruby?

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

Что-то вроде:

myfile = File.open("path/to/file.txt", "r+")

myfile.each do |line|
    myfile.replace_puts('blah') if line =~ /myregex/
end

myfile.close

Где replace_puts будет писать поверх текущей строкивместо (перезаписи) записи следующей строки, как это происходит в настоящее время, поскольку указатель находится в конце строки (после разделителя).

Таким образом, каждая строка, соответствующая /myregex/, будет заменена на 'бла».Очевидно, что я имею в виду немного больше, чем это, что касается обработки, и будет сделано в одну строку, но идея та же - я хочу читать файл построчно, редактировать определенные строки, ивыпиши, когда я закончу.

Может быть, есть способ просто сказать «вернуться назад сразу после последнего разделителя»?Или каким-то образом использовать each_with_index и писать через индексный номер строки?Я не смог найти ничего подобного.

Лучшее решение, которое у меня есть, - это читать вещи построчно, записывать их в новый (временный) файл построчно (возможно, редактировать), затем перезаписать старый файл новым временным файлом и удалить.Опять же, я чувствую, что должен быть лучший способ - я не думаю, что мне нужно было бы создавать новый 1-гигабайтный файл, просто чтобы отредактировать некоторые строки в существующем 1-гигабайтном файле.

Ответы [ 4 ]

68 голосов
/ 09 декабря 2010

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

Существуют две основные модели для изменения группы строк.Если файл не слишком большой, просто прочитайте все это в память, измените его и запишите обратно.Например, добавление «Килрой был здесь» в начале каждой строки файла:

path = '/tmp/foo'
lines = IO.readlines(path).map do |line|
  'Kilroy was here ' + line
end
File.open(path, 'w') do |file|
  file.puts lines
end

Несмотря на простоту, у этого метода есть опасность: если программа прерывается во время записи файла, вы будетепотерять часть или все это.Также необходимо использовать память для хранения всего файла.Если любой из этих вопросов вызывает беспокойство, вы можете предпочесть следующий метод.

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

require 'tempfile'
require 'fileutils'

path = '/tmp/foo'
temp_file = Tempfile.new('foo')
begin
  File.open(path, 'r') do |file|
    file.each_line do |line|
      temp_file.puts 'Kilroy was here ' + line
    end
  end
  temp_file.close
  FileUtils.mv(temp_file.path, path)
ensure
  temp_file.close
  temp_file.unlink
end

Поскольку переименование (FileUtils.mv) является атомарным, перезаписанный входной файл появится сразу.Если программа прервана, либо файл будет перезаписан, либо нет.Нет возможности его частичной перезаписи.

Предложение ensure не является строго необходимым: файл будет удален, когда экземпляр Tempfile будет собирать мусор.Однако это может занять некоторое время.Блок ensure обеспечивает немедленную очистку временного файла, не дожидаясь его сбора.

8 голосов
/ 09 декабря 2010

Если вы хотите перезаписывать файл построчно, вам нужно убедиться, что новая строка имеет ту же длину, что и исходная строка.Если новая строка длиннее, ее часть будет записана на следующей строке.Если новая строка короче, остальная часть старой строки просто остается там, где она есть.Решение с временным файлом действительно намного безопаснее.Но если вы готовы пойти на риск:

File.open('test.txt', 'r+') do |f|   
    old_pos = 0
    f.each do |line|
        f.pos = old_pos   # this is the 'rewind'
        f.print line.gsub('2010', '2011')
        old_pos = f.pos
    end
end

Если размер линии действительно изменится, это возможно:

File.open('test.txt', 'r+') do |f|   
    out = ""
    f.each do |line|
        out << line.gsub(/myregex/, 'blah') 
    end
    f.pos = 0                     
    f.print out
    f.truncate(f.pos)             
end
2 голосов
/ 21 января 2015

На всякий случай, если вы используете Rails или Facets , или вы по-другому зависите от Rails ' ActiveSupport , вы можете использовать расширение atomic_write для File :

File.atomic_write('path/file') do |file|
  file.write('your content')
end

За кулисами это создаст временный файл, который впоследствии будет перемещен по нужному пути, заботясь о закрытии файла для вас.

Кроме того, он клонирует права доступа к файлу для существующего файла или, если его нет, для текущего каталога.

0 голосов
/ 07 января 2017

Вы можете писать в середине файла, но вы должны быть осторожны, чтобы сохранить длину строки, которую вы перезаписываете, то же самое, в противном случае вы перезаписываете часть следующего текста.Я привожу здесь пример, используя File.seek, IO :: SEEK_CUR дает текущую позицию указателя файла, в конце строки, которая только что прочитана, +1 для символа CR в конце строки.

look_for     = "bbb"
replace_with = "xxxxx"

File.open(DATA, 'r+') do |file|
  file.each_line do |line|
    if (line[look_for])
      file.seek(-(line.length + 1), IO::SEEK_CUR)
      file.write line.gsub(look_for, replace_with)
    end
  end
end
__END__
aaabbb
bbbcccddd
dddeee
eee

После выполнения, в конце сценария у вас теперь есть следующее, а не то, что вы имели в виду, я предполагаю.

aaaxxxxx
bcccddd
dddeee
eee

Учитывая это, скорость, с помощью которой этоТехника намного лучше, чем классический метод «чтение и запись в новый файл».Смотрите эти тесты в файле с музыкальными данными размером 1,7 ГБ.Для классического подхода я использовал технику Уэйна.Тест выполняется с помощью метода .bmbm, поэтому кэширование файла не играет большой роли.Тесты выполняются с помощью MRI Ruby 2.3.0 в Windows 7. Строки были эффективно заменены, я проверил оба метода.

require 'benchmark'
require 'tempfile'
require 'fileutils'

look_for      = "Melissa Etheridge"
replace_with  = "Malissa Etheridge"
very_big_file = 'D:\Documents\muziekinfo\all.txt'.gsub('\\','/')

def replace_with file_path, look_for, replace_with
  File.open(file_path, 'r+') do |file|
    file.each_line do |line|
      if (line[look_for])
        file.seek(-(line.length + 1), IO::SEEK_CUR)
        file.write line.gsub(look_for, replace_with)
      end
    end
  end
end

def replace_with_classic path, look_for, replace_with
  temp_file = Tempfile.new('foo')
  File.foreach(path) do |line|
    if (line[look_for])
      temp_file.write line.gsub(look_for, replace_with)
    else
      temp_file.write line
    end
  end
  temp_file.close
  FileUtils.mv(temp_file.path, path)
ensure
  temp_file.close
  temp_file.unlink
end

Benchmark.bmbm do |x| 
  x.report("adapt          ") { 1.times {replace_with very_big_file, look_for, replace_with}}
  x.report("restore        ") { 1.times {replace_with very_big_file, replace_with, look_for}}
  x.report("classic adapt  ") { 1.times {replace_with_classic very_big_file, look_for, replace_with}}
  x.report("classic restore") { 1.times {replace_with_classic very_big_file, replace_with, look_for}}
end 

, что дало

Rehearsal ---------------------------------------------------
adapt             6.989000   0.811000   7.800000 (  7.800598)
restore           7.192000   0.562000   7.754000 (  7.774481)
classic adapt    14.320000   9.438000  23.758000 ( 32.507433)
classic restore  14.259000   9.469000  23.728000 ( 34.128093)
----------------------------------------- total: 63.040000sec

                      user     system      total        real
adapt             7.114000   0.718000   7.832000 (  8.639864)
restore           6.942000   0.858000   7.800000 (  8.117839)
classic adapt    14.430000   9.485000  23.915000 ( 32.195298)
classic restore  14.695000   9.360000  24.055000 ( 33.709054)

Таким образом, замена in_file была 4раз быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...