代码之家  ›  专栏  ›  技术社区  ›  Rickard Lindberg

如何安全地写入文件?

  •  15
  • Rickard Lindberg  · 技术社区  · 15 年前

    write

    覆盖现有文件(开始写入原始文件)显然不安全。如果

    一个更好的选择是写信给 在某处归档,以及 复制 将临时文件转换为原始文件。

    在POSIX系统上,我想您可以使用 rename 系统调用,这是一种原子操作。但是,在Windows系统上如何才能做到最好呢?特别是,您如何以最佳方式处理此问题 ?

    8 回复  |  直到 15 年前
        1
  •  16
  •   Shailesh Kumar    15 年前

    如果您看到Python的文档,它会清楚地提到os.rename()是一个原子操作。因此,在您的情况下,将数据写入临时文件,然后将其重命名为原始文件是非常安全的。

    • 将原始文件设为abc.xml
    • 将abc.xml重命名为abc.xml.bak
    • 将abc.xml.tmp重命名为abc.xml

    如您所见,您随身携带了abc.xml.bak,如果tmp文件存在任何相关问题,您可以使用它进行恢复并将其复制回来。

        2
  •  13
  •   u0b34a0f6ae    15 年前

    如果您想准确无误地进行保存,您必须:

    1. 写入临时文件
    2. 冲洗 fsync fdatasync )

    请注意,调用fsync会对性能产生不可预测的影响——因此,ext3上的Linux可能会暂停磁盘I/O整秒,这取决于其他未完成的I/O。

    注意 rename POSIX中的原子操作——至少不像您所期望的那样与文件数据相关。然而,大多数操作系统和文件系统将以这种方式工作。但您似乎错过了关于Ext4和关于原子性的文件系统保证的大型linux讨论。我不知道确切的链接位置,但这里是一个开始: ext4 and data loss .

    然而,问题是,即使不是所有的文件系统,也是大多数文件系统将元数据和数据分开。重命名只是元数据。这听起来可能很可怕,但文件系统重视元数据而不是数据(以HFS+或Ext3,4中的日志记录为例)!原因是元数据较轻,如果元数据已损坏,则整个文件系统都已损坏——文件系统当然必须保存它自己,然后按顺序保存用户的数据。

    Ext4确实打破了规则 改名 重命名失败,但重命名成功。Ext4可能成功注册了重命名,但如果不久之后发生崩溃,则无法写出文件数据。结果是一个长度为0的文件,既不是原始数据,也不是新数据。

        3
  •  5
  •   Michal Sznajder    15 年前

    在WinAPI中,我发现了非常好的函数 ReplaceFile DeleteFile , MoveFile 联合体。

        4
  •  5
  •   miku    4 年前

    一个过于简单的解决方案。使用 tempfile 要创建临时文件,如果写入成功,只需将该文件重命名为原始配置文件。

    注意 rename 不是原子的 跨文件系统。为了真正安全,您必须求助于一种轻微的变通方法(例如,在目标文件系统上使用tempfile,然后重命名)。

    有关锁定文件的信息,请参见 portalocker

        5
  •  4
  •   S.Lott    15 年前

    标准溶液是这个。

    1. 使用类似的名称编写一个新文件。例如,X.ext#。

      • X.ext(原件)至X.ext~

      • X.ext#(新的)到X.ext

    任何时候都不会有任何损失或腐败。唯一的故障可能发生在重命名过程中。但你没有丢失或损坏任何东西。原始文件在最终重命名之前可以恢复。

        6
  •  3
  •   Mahmoud Hashemi    9 年前

    现在有了一个编码的、纯Python,我敢说,在 boltons utility library : 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')
    

    all well-documented . 完全公开,我是《博尔顿》的作者,但这一部分是在大量社区帮助下建立的。不要犹豫 drop a note 如果有不清楚的地方!

        7
  •  2
  •   Jason R. Coombs    15 年前

    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
    

    然后我用这个脚本测试了这个行为。

    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
  •  0
  •   unutbu    15 年前

    您可以使用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
    

    如果脚本突然结束,您仍将拥有备份。