Удалить первые две строки и добавить две строки в файл

У меня есть текстовый файл, который начинается с:

Title

aaa
bbb
ccc

Я не знаю, что будет в строке, но я знаю, что структура файла будет Title, затем пустая строка, затем фактические строки. Я хочу изменить это на:

New Title

fff
aaa
bbb
ccc

Я имел в виду это:

lineArray = File.readlines(destinationFile).drop(2)
lineArray.insert(0, 'fff\n')
lineArray.insert(0, '\n')
lineArray.insert(0, 'new Title\n')
File.writelines(destinationFile, lineArray)

но writelines не существует

`writelines' for File:Class (NoMethodError)

Есть ли способ удалить первые две строки файла и добавить три новые строки?

4 ответа

Решение

Я бы начал с чего-то вроде этого:

NEWLINES = {
  0 => "New Title",
  1 => "\nfff"
}

File.open('test.txt.new', 'w') do |fo|
  File.foreach('test.txt').with_index do |li, ln|
    fo.puts (NEWLINES[ln] || li)
  end
end

Вот содержимое test.txt.new после запуска:

New Title

fff
aaa
bbb
ccc

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

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

NEWLINES = [
  "New Title",
  "",
  "fff"
]

file = File.readlines('test.txt')
File.open('test.txt.new', 'w') do |fo|
  fo.puts NEWLINES
  fo.puts file[(NEWLINES.size - 1) .. -1]
end

Это не очень умно, но это будет работать для простых замен.

Если вы действительно хотите сделать это правильно, узнайте, как diff работает, создайте файл diff, а затем позвольте ему выполнять тяжелую работу, поскольку он предназначен для решения подобных задач, работает очень быстро и используется миллионы раз в день в системах *nix по всему миру.

Используйте put со всем массивом:

File.open("destinationFile", "w+") do |f|
   f.puts(lineArray)
end

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

Сначала определите текст замены:

require "stringio"

replacement = StringOI.new <<END
New Title

fff
END

Я сделал это объектом StringIO, но это также может быть объект File, если ваш текст замены находится в файле.

Теперь откройте файл назначения (новый файл) и запишите в него каждую строку из текста замены.

dest = File.open(dest_fn, 'wb') do |dest|
replacement.each_line {|ln| dest << ln }

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

Затем откройте исходный файл и ищите вперед, позвонив getsreplacement.lineno раз:

orig = File.open(orig_fn, 'r')
replacement.lineno.times { orig.gets }

Наконец, запишите оставшиеся строки из исходного файла в новый файл. Мы сделаем это более эффективно на этот раз с File.copy_stream:

File.copy_stream(orig, dest)
orig.close
dest.close

Вот и все. Конечно, это перетаскивание, закрывающее эти файлы вручную (и когда мы это сделаем, мы должны сделать это в ensure блок), так что лучше использовать блочную форму File.open чтобы автоматически закрыть их. Кроме того, мы можем переместить orig.gets звонки в replacement.each_line цикл:

File.open(dest_fn, 'wb') do |dest|
  File.open(orig_fn, 'r') do |orig|
    replacement.each_line {|ln| dest << ln; orig.gets }
    File.copy_stream(orig, dest)
  end
end

Сначала создайте входной тестовый файл.

FNameIn = "test_in"

text = <<_
Title

How now,
brown cow?
_
  #=> "Title\n\nHow now,\nbrown cow?\n" 

File.write(FNameIn, text)
  #=> 27

Теперь читайте и пишите построчно.

FNameOut = "test_out"

File.open(FNameIn) do |fin|
  fin.gets; fin.gets  
  File.open(FNameOut, 'w') do |fout|
    fout.puts "New Title"
    fout.puts
    fout.puts "fff"
    until fin.eof?    
      fout.puts fin.gets
    end
  end
end

Проверьте результат:

puts File.read(FNameOut)
  # New Title
  #
  # fff
  # How now,
  # brown cow?

Ruby закроет каждый из двух файлов, когда его блок завершится.

Если файлы не большие, вы можете написать:

File.write(FNameOut,
  ["New Title\n", "\n", "fff\n"].concat(File.readlines(FNameIn).drop(2)).join)
Другие вопросы по тегам