2009-11-28 41 views
15

Hãy tưởng tượng bạn có một thư viện để làm việc với một số loại tập tin cấu hình hoặc tệp XML. Thư viện đọc toàn bộ tệp vào bộ nhớ và cung cấp các phương thức để chỉnh sửa nội dung. Khi bạn hoàn thành thao tác nội dung, bạn có thể gọi số write để lưu nội dung vào tệp. Câu hỏi đặt ra là làm thế nào để làm điều này một cách an toàn.Làm cách nào để ghi một tệp an toàn?

Ghi đè tệp hiện có (bắt đầu ghi vào tệp gốc) rõ ràng là không an toàn. Nếu phương pháp write thất bại trước khi nó được thực hiện, bạn kết thúc với một nửa tập tin bằng văn bản và bạn đã bị mất dữ liệu.

Một lựa chọn tốt hơn sẽ được ghi vào một tập tin tạm thời nơi nào đó, và khi các phương pháp write đã hoàn tất, bạn sao chép các tập tin tạm thời vào file gốc.

Bây giờ, nếu bản sao bằng cách nào đó không thành công, bạn vẫn lưu chính xác dữ liệu trong tệp tạm thời. Và nếu bản sao thành công, bạn có thể xóa tệp tạm thời.

Trên hệ thống POSIX, tôi đoán bạn có thể sử dụng cuộc gọi hệ thống rename là một hoạt động nguyên tử. Nhưng làm thế nào bạn sẽ làm điều này tốt nhất trên một hệ thống Windows? Cụ thể, cách bạn xử lý tốt nhất điều này bằng cách sử dụng Python?

Ngoài ra, còn có một lược đồ khác để ghi một cách an toàn vào tệp không?

+0

Tại sao phải sao chép? Tại sao không đổi tên? –

Trả lời

14

Nếu bạn thấy tài liệu của Python, nó đề cập rõ ràng rằng os.rename() là một hoạt động nguyên tử. Vì vậy, trong trường hợp của bạn, ghi dữ liệu vào một tệp tạm thời và sau đó đổi tên nó thành tệp gốc sẽ khá an toàn.

Một cách khác có thể làm việc như thế này:

  • phép tập tin gốc được abc.xml
  • tạo abc.xml.tmp và ghi dữ liệu mới để nó
  • đổi tên abc.xml để abc.xml bak
  • đổi tên abc.xml.tmp để abc.xml
  • sau abc.xml mới được đưa vào vị trí, loại bỏ abc.xml.bak

Như bạn có thể thấy rằng bạn có tệp abc.xml.bak với bạn mà bạn có thể sử dụng để khôi phục nếu có bất kỳ vấn đề nào liên quan đến tệp tmp và sao chép nó trở lại.

+0

Điều này tương tự như câu trả lời của S.Lott với phần bổ sung để xóa tệp sao lưu. Có vẻ như đó là cách tốt nhất để làm điều đó. –

+0

Tôi thực sự thấy việc thực hiện này theo cách ZODB (Cơ sở dữ liệu đối tượng Zope) thực hiện việc đóng gói tệp cơ sở dữ liệu của nó (Data.fs) tức là loại bỏ không gian không sử dụng của các giao dịch cũ hơn từ tệp cơ sở dữ liệu. Mã này là mã python thường xuyên, đóng gói được thực hiện trong một tập tin tạm thời và sau đó các bước tương tự như trên được thực hiện. ZODB đã có mặt trong nhiều năm và hoạt động tốt trên cả nền tảng Windows và POSIX, vì vậy tôi tin rằng phương pháp này sẽ hoạt động. –

+3

Python không thể thực thi bảo đảm đổi tên là nguyên tử. Theo như tôi biết, nó chỉ là kêu gọi hệ thống của hệ điều hành. Tuy nhiên, thủ tục bạn thực hiện tốt. –

4

Trong Win API, tôi thấy chức năng khá đẹp ReplaceFile có tên gì cho thấy ngay cả với tùy chọn sao lưu. Luôn có cách kết hợp với DeleteFile, MoveFile kết hợp.

Nói chung những gì bạn muốn làm là thực sự tốt. Và tôi không thể nghĩ ra một kế hoạch viết nào tốt hơn.

+3

Sẽ tốt hơn nếu bạn minh họa rằng với mã Python thích hợp gọi API MS thư viện. – RedGlyph

+1

Tôi không nhận ra ReplaceFile đã tồn tại. Đọc tài liệu, nó dường như làm nhiều hơn là chỉ đổi tên. Nó duy trì nhiều thuộc tính của tập tin được thay thế, vì vậy nó có vẻ được thiết kế đặc biệt cho mục đích này. –

3

Một giải pháp đơn giản. Sử dụng tempfile để tạo tệp tạm thời và nếu viết thành công, chỉ cần đổi tên tệp thành tệp cấu hình ban đầu của bạn.

Để khóa tệp, hãy xem portalocker.

+1

Nếu tempfile được tạo ra trong một hệ thống tập tin khác so với một đích, thì việc đổi tên cuối cùng sẽ không hoạt động, hoặc nó sẽ không phải là nguyên tử. – tzot

+0

Nhưng trừ khi đổi tên là nguyên tử, tôi có nguy cơ mất dữ liệu, phải không? –

4

Giải pháp tiêu chuẩn là điều này.

  1. Viết tệp mới có tên tương tự. Ví dụ X.ext #.

  2. Khi tệp đó đã bị đóng (và thậm chí có thể đã đọc và được kiểm tra), thì bạn có hai lần đổi tên.

    • X.ext (bản gốc) để X.ext ~

    • X.ext # (mới) để X.ext

  3. (Chỉ dành cho điên paranoids) gọi chức năng đồng bộ hóa hệ điều hành để buộc ghi đệm bẩn.

Không bị mất hoặc hỏng bất cứ lúc nào. Sự trục trặc duy nhất có thể xảy ra trong quá trình đổi tên. Nhưng bạn đã không mất bất cứ điều gì hoặc làm hỏng bất cứ điều gì. Bản gốc có thể phục hồi ngay cho đến khi đổi tên cuối cùng.

+0

Nhưng nếu bạn không đổi tên hai lần (tạo bản sao lưu như bạn đã làm), bạn có nguy cơ mất dữ liệu nếu đổi tên không phải là nguyên tử, phải không? –

+1

Đổi tên * là * nguyên tử trong nhiều hệ điều hành. Tuy nhiên, bản sao lưu quan trọng hơn việc vắt tay về nguyên tử của hoạt động. Hãy nhớ rằng: tỷ lệ cược của một vụ tai nạn (ngoại trừ trong Windows) là rất nhỏ. Các tỷ lệ cược của một vụ tai nạn ở giữa một đổi tên (mà chỉ là một vài hướng dẫn cộng với một đồng bộ là rất, rất nhỏ. –

11

Nếu bạn muốn trở thành POSIXly chính xác và tiết kiệm bạn phải:

  1. Viết thư cho tập tin tạm thời
  2. Flush và fsync file (hoặc fdatasync)
  3. Rename so với file gốc

Lưu ý rằng việc gọi fsync có các hiệu ứng không thể đoán trước về hiệu suất - Linux trên ext3 có thể dừng cho toàn bộ số giây I/O của đĩa theo kết quả, tùy thuộc vào o khác biết rõ I/O.

Lưu ý rằng renamekhông hoạt động nguyên tử trong POSIX - ít nhất là không liên quan đến dữ liệu tệp như bạn mong đợi. Tuy nhiên, hầu hết các hệ điều hành và hệ thống tập tin sẽ hoạt động theo cách này. Nhưng có vẻ như bạn đã bỏ lỡ cuộc thảo luận Linux rất lớn về Ext4 và hệ thống tập tin đảm bảo về nguyên tử. Tôi không biết chính xác nơi để liên kết nhưng đây là một sự khởi đầu: ext4 and data loss.

Tuy nhiên, lưu ý rằng trên nhiều hệ thống, việc đổi tên sẽ an toàn trong thực tế như bạn mong đợi. Tuy nhiên nó là một cách không thể có được cả hai - hiệu suất và độ tin cậy trên tất cả các confiugrations linux có thể!

Với ghi vào tệp tạm thời, sau đó đổi tên tệp tạm thời, người ta sẽ mong đợi các hoạt động phụ thuộc và sẽ được thực hiện theo thứ tự.

Tuy nhiên, vấn đề là hầu hết, nếu không phải tất cả siêu dữ liệu và dữ liệu riêng biệt của hệ thống tệp. Đổi tên chỉ là siêu dữ liệu. Nghe có vẻ khủng khiếp với bạn, nhưng các hệ thống tập tin có giá trị siêu dữ liệu trên dữ liệu (ví dụ như ghi nhật ký trong HFS + hoặc Ext3,4)! Lý do là siêu dữ liệu nhẹ hơn và nếu siêu dữ liệu bị hỏng, toàn bộ hệ thống tệp bị hỏng - hệ thống tệp phải dĩ nhiên bảo vệ nó, sau đó bảo toàn dữ liệu của người dùng theo thứ tự đó.

Ext4 đã phá vỡ rename kỳ vọng khi nó xuất hiện lần đầu tiên, tuy nhiên các chẩn đoán đã được thêm vào để giải quyết. Vấn đề là không phải đổi tên không thành công, nhưng đổi tên thành công. Ext4 có thể đăng ký đổi tên thành công, nhưng không ghi dữ liệu tệp nếu xảy ra sự cố ngay sau đó. Kết quả là sau đó một tập tin 0-chiều dài và không phải orignal cũng không phải dữ liệu mới.

Vì vậy, trong ngắn hạn, POSIX không đảm bảo như vậy. Đọc bài viết liên quan đến Ext4 để biết thêm thông tin!

+0

Có lẽ tôi hiểu lầm. Nhưng nếu bạn đổi tên trên một hệ thống POSIX, không phải bạn bảo đảm rằng Nếu chức năng đổi tên() không thành công vì bất kỳ lý do nào khác ngoài [EIO], thì bất kỳ tập tin nào được đặt tên mới sẽ không bị ảnh hưởng. "Tôi đoán nó vẫn có thể để lại dữ liệu bị hỏng rồi. –

+0

vấn đề là đổi tên thành công, và đó chỉ là một sự đổi tên không đảm bảo nguyên tử của toàn bộ hoạt động – u0b34a0f6ae

+1

Ý bạn là đổi tên() không phải là điểm kiểm tra. Nó chắc chắn là nguyên tử, xem: http://www.opengroup.org/onlinepubs /009695399/functions/rename.html – geocar

1

Theo đề xuất của RedGlyph, tôi đã thêm một triển khai ReplaceFile sử dụng ctypes để truy cập API Windows. Lần đầu tiên tôi thêm vào 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 

Sau đó, tôi đã kiểm tra hành vi bằng tập lệnh này.

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') 

Trong khi điều này chỉ hoạt động trong Windows, có vẻ như rất nhiều tính năng đẹp khác thay thế thường trình sẽ thiếu. Xem chi tiết API docs.

0

Bạn có thể sử dụng các mô-đun fileinput để xử lý sự ủng hộ-up và tại chỗ bằng văn bản cho bạn:

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) 

Nếu bạn cần phải đọc trong toàn bộ nội dung trước khi bạn có thể viết xuống dòng của, sau đó bạn có thể làm điều đó trước tiên, sau đó in toàn bộ nội dung mới bằng

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

Nếu tập lệnh kết thúc đột ngột, bạn vẫn sẽ có bản sao lưu.

3

Hiện tại, đã có một Python thuần hóa, thuần khiết và tôi dám nói giải pháp Pythonic cho điều này trong số boltons utility library: boltons.fileutils.atomic_save.

Chỉ pip install boltons, sau đó:

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') 

Có rất nhiều lựa chọn thực tế, all well-documented. Tiết lộ đầy đủ, tôi là tác giả của bolton, nhưng phần đặc biệt này được xây dựng với rất nhiều trợ giúp cộng đồng. Đừng ngần ngại drop a note nếu có điều gì đó không rõ ràng!

Các vấn đề liên quan