Подтвердить что ты не робот

Как безопасно писать в файл?

Представьте, что у вас есть библиотека для работы с каким-либо файлом XML или файлом конфигурации. Библиотека читает весь файл в памяти и предоставляет методы для редактирования содержимого. Когда вы закончите манипулирование контентом, вы можете вызвать write, чтобы сохранить содержимое обратно в файл. Вопрос заключается в том, как сделать это безопасным способом.

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

Лучшим вариантом было бы записать в файл временный где-нибудь, а когда метод write закончен, вы скопируете временный файл в исходный файл.

Теперь, если копия как-то терпит неудачу, вы все равно правильно сохранили данные во временном файле. И если копия завершается успешно, вы можете удалить временный файл.

В системах POSIX, я думаю, вы можете использовать системный вызов rename, который является атомной операцией. Но как бы вы сделали это лучше всего в системе Windows? В частности, как вы справляетесь с этим с помощью Python?

Кроме того, существует ли другая схема безопасного написания файлов?

4b9b3361

Ответ 1

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

Другой способ может работать следующим образом:

  • пусть исходный файл будет abc.xml
  • создайте abc.xml.tmp и напишите ему новые данные.
  • переименовать abc.xml в abc.xml.bak
  • переименовать abc.xml.tmp в abc.xml
  • после того, как новый abc.xml правильно вставлен на место, удалите abc.xml.bak

Как вы можете видеть, у вас есть файл abc.xml.bak, который вы можете использовать для восстановления, если есть какие-либо проблемы, связанные с файлом tmp и его копированием.

Ответ 2

Если вы хотите быть POSIXly правильным и сохранить, вам нужно:

  • Запись во временный файл
  • Flush и fsync файл (или fdatasync)
  • Переименовать исходный файл

Обратите внимание, что вызов fsync имеет непредсказуемые последствия для производительности. В результате Linux на ext3 может останавливаться на диск ввода-вывода целых чисел в секундах в зависимости от других выдающихся операций ввода-вывода.

Обратите внимание, что rename - это не атомная операция в POSIX - по крайней мере, по отношению к файлам, как вы ожидаете. Однако большинство операционных систем и файловых систем будут работать таким образом. Но, похоже, вы пропустили очень большую дискуссию по Linux о проблемах с Ext4 и файловой системой об атомарности. Я не знаю, где именно ссылку, но вот начало: ext4 и потеря данных.

Обратите внимание, что во многих системах переименование будет таким же безопасным на практике, как вы ожидаете. Однако в обоих случаях невозможно получить как производительность, так и надежность во всех возможных связях Linux!

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

Однако проблема заключается в том, что большинство, если не все файловые системы, разделяют метаданные и данные. Переименование - это только метаданные. Это может показаться вам ужасным, но файловые системы оценивают метаданные по данным (например, для ведения журнала в HFS + или Ext3,4)! Причина в том, что метаданные легче, и если метаданные повреждены, вся файловая система повреждена - файловая система должна, конечно, сохранить ее самостоятельно, а затем сохранить пользовательские данные в этом порядке.

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

Итак, POSIX не дает такой гарантии. Прочтите связанную статью Ext4 для получения дополнительной информации!

Ответ 3

В Win API я нашел довольно приятную функцию ReplaceFile, которая делает то, что предлагает название, даже с дополнительным резервным копированием. Всегда существует способ DeleteFile, MoveFile комбо.

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

Ответ 4

Стандартное решение - это.

  • Напишите новый файл с похожим именем. X.ext # например.

  • Когда этот файл был закрыт (и, возможно, даже прочитан и запрограммирован), вы два двух переименования.

    • X.ext(оригинал) на X.ext ~

    • X.ext # (новый) в X.ext

  • (Только для сумасшедших параноиков) вызовите функцию синхронизации ОС, чтобы заставить грязные записи буфера.

Ни в коем случае ничего не теряется или не может быть повреждено. Единственный сбой может произойти во время переименований. Но вы ничего не потеряли или ничего не испортили. Оригинал восстанавливается вплоть до окончательного переименования.

Ответ 5

Упрощенное решение. Используйте tempfile, чтобы создать временный файл, и если запись удастся просто переименовать файл в исходный файл конфигурации.

Для блокировки файла см. portalocker.

Ответ 6

Теперь есть кодифицированный, чистый-Python, и я смею сказать, что Pythonic это решение в библиотеке утилиты boltons: boltons.fileutils.atomic_save.

Просто pip install boltons, затем:

from boltons.fileutils import atomic_save

with atomic_save('/path/to/file.txt') as f:
    f.write('this will only overwrite if it succeeds!\n')

Существует множество практических вариантов все хорошо документированные. Полное раскрытие, я автор болтонов, но эта конкретная часть была построена с большой помощью сообщества. Не стесняйтесь отбросить заметку, если что-то неясно!

Ответ 7

В рекомендации RedGlyph я добавил реализацию ReplaceFile, которая использует ctypes для доступа к API Windows. Я сначала добавил это в jaraco.windows.api.filesystem.

ReplaceFile = windll.kernel32.ReplaceFileW
ReplaceFile.restype = BOOL
ReplaceFile.argtypes = [
    LPWSTR,
    LPWSTR,
    LPWSTR,
    DWORD,
    LPVOID,
    LPVOID,
    ]

REPLACEFILE_WRITE_THROUGH = 0x1
REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2
REPLACEFILE_IGNORE_ACL_ERRORS = 0x4

Затем я проверил поведение, используя этот script.

from jaraco.windows.api.filesystem import ReplaceFile
import os

open('orig-file', 'w').write('some content')
open('replacing-file', 'w').write('new content')
ReplaceFile('orig-file', 'replacing-file', 'orig-backup', 0, 0, 0)
assert open('orig-file').read() == 'new content'
assert open('orig-backup').read() == 'some content'
assert not os.path.exists('replacing-file')

Пока это работает только в Windows, у него, похоже, много приятных функций, которых не хватало бы для других подпрограмм. Подробнее см. API docs.

Ответ 8

Вы можете использовать модуль fileinput для обработки резервных копий и записи на месте для вас:

import fileinput
for line in fileinput.input(filename,inplace=True, backup='.bak'):
    # inplace=True causes the original file to be moved to a backup
    # standard output is redirected to the original file.
    # backup='.bak' specifies the extension for the backup file.

    # manipulate line
    newline=process(line)
    print(newline)

Если вам нужно прочитать все содержимое, прежде чем писать новую строку, то вы можете сделать это сначала, а затем распечатать все новое содержимое с помощью

newcontents=process(contents)
for line in fileinput.input(filename,inplace=True, backup='.bak'):
    print(newcontents)
    break

Если script заканчивается внезапно, у вас все равно будет резервная копия.