Как удалить строки данных в середине текстового файла с помощью Ruby

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

Я не знаю разницы между append и write.

например.

Если у меня есть файл, содержащий:

Person1,will,23
Person2,Richard,32
Person3,Mike,44

Как мне удастся удалить строку, содержащую Person2?

Ответ 1

Вы можете удалить строку несколькими способами:

  • Имитировать удаление. То есть просто переписывайте содержимое строки пробелами. Позже, когда вы читаете и обрабатываете файл, просто игнорируйте такие пустые строки.

    Преимущества: это легко и быстро. Концы: это не реальное удаление данных (файл не сжимается), и вам нужно делать больше работы при чтении/обработке файла.

    код:

    f = File.new(filename, 'r+')
    f.each do |line|
      if should_be_deleted(line)
        # seek back to the beginning of the line.
        f.seek(-line.length, IO::SEEK_CUR)
    
        # overwrite line with spaces and add a newline char
        f.write(' ' * (line.length - 1))
        f.write("\n")
      end
    end
    f.close
    
    File.new(filename).each {|line| p line }
    
    # >> "Person1,will,23\n"
    # >> "                  \n"
    # >> "Person3,Mike,44\n"
    
  • Сделайте реальное удаление. Это означает, что строка больше не будет существовать. Поэтому вам нужно будет прочитать следующую строку и переписать текущую строку. Затем повторите это для всех следующих строк до тех пор, пока не будет достигнут конец файла. Это, похоже, задача с ошибкой (строки разной длины и т.д.), Поэтому здесь существует безошибочная альтернатива: открыть файл temp, записать в него строки до (но не включая) строки, которую вы хотите удалить, пропустить строку, которую вы хотите удалить, записать остальные в файл temp. Удалите исходный файл и переименуйте временный, чтобы использовать его имя. Готово.

    Хотя это технически полная переписывание файла, он отличается от того, что вы просили. Файл не нужно полностью загружать в память. Вам нужна только одна строка за раз. Ruby предоставляет метод для этого: IO # each_line.

    Профи: никаких предположений. Строки удаляются. Код чтения не должен изменяться. Минусы: при удалении строки (не только код, но и время ввода/вывода/ЦП) выполняется больше работы.

    Существует фрагмент, который иллюстрирует этот подход в @azgult answer.

Ответ 2

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

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

require 'fileutils'

open('file.txt', 'r') do |f|
  open('file.txt.tmp', 'w') do |f2|
    f.each_line do |line|
       f2.write(line) unless line.start_with? "Person2"
    end
  end
end
FileUtils.mv 'file.txt.tmp', 'file.txt'

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

Ответ 3

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

File.open('output_file_path', 'w') do |output| # 'w' for a new file, 'a' append to existing
  File.open('input_file_path', 'r') do |input|
    line = input.readline
    if keep_line(line) # logic here to determine if the line should be kept
      output.write(line)
    end
  end
end

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

Просмотрите параметры метода чтения и прочитайте о поиске здесь:

http://ruby-doc.org/core-2.0/IO.html#method-i-read

Ответ 4

Прочитайте здесь:

File.open('output.txt', 'w') do |out_file|
  File.open('input.txt', 'r').each do |line|
    out_file.print line.sub('Person2', '')
  end
end