3

バックアップ スクリプトをシェルから Python に変換する作業を行っています。私の古いスクリプトの機能の 1 つは、 gzip -t を実行して、作成された tarfile の整合性をチェックすることでした。

これは Python では少し難しいようです。

これを行う唯一の方法は、tarfile 内の圧縮された TarInfo オブジェクトをそれぞれ読み取ることです。

ディスクに抽出したり、メモリに (全体として) 保持したりせずに、tarfile の整合性をチェックする方法はありますか?

#python on freenode の善良な人々は、各 TarInfo オブジェクトをチャンクごとに読み取り、読み取ったチャンクを破棄することを提案しました。

Python を始めたばかりなので、これを行う方法がわからないことを認めなければなりません。

1kb から 10GB までの範囲のファイルを含む 30GB の tarfile があると想像してください...

これは私が書き始めた解決策です:

try:
    tardude = tarfile.open("zero.tar.gz")
except:
    print "There was an error opening tarfile. The file might be corrupt or missing."

for member_info in tardude.getmembers():
    try:
        check = tardude.extractfile(member_info.name)
    except:
        print "File: %r is corrupt." % member_info.name

tardude.close()

このコードはまだ完成していません。30 GB の巨大な tar アーカイブでこれを実行することは敢えてしません。ある時点で、check は 10 GB 以上のオブジェクトになるためです (tar アーカイブ内にそのような巨大なファイルがある場合)。

おまけ: zero.tar.gz を手動で破損してみました (16 進エディター - 数バイトの中間ファイルを編集)。最初のexceptはIOErrorをキャッチしません...出力は次のとおりです。

Traceback (most recent call last):
  File "./test.py", line 31, in <module>
    for member_info in tardude.getmembers():
  File "/usr/lib/python2.7/tarfile.py", line 1805, in getmembers
    self._load()        # all members, we first have to
  File "/usr/lib/python2.7/tarfile.py", line 2380, in _load
    tarinfo = self.next()
  File "/usr/lib/python2.7/tarfile.py", line 2315, in next
    self.fileobj.seek(self.offset)
  File "/usr/lib/python2.7/gzip.py", line 429, in seek
    self.read(1024)
  File "/usr/lib/python2.7/gzip.py", line 256, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 320, in _read
    self._read_eof()
  File "/usr/lib/python2.7/gzip.py", line 342, in _read_eof
    hex(self.crc)))
IOError: CRC check failed 0xe5384b87 != 0xdfe91e1L
4

3 に答える 3

3

物事をもう少し慣用的にするために、 Ayaの答えを少し改善しました(ただし、メカニズムをより見やすくするためにエラーチェックの一部を削除しています):

BLOCK_SIZE = 1024

with tarfile.open("zero.tar.gz") as tardude:
    for member in tardude.getmembers():
        with tardude.extractfile(member.name) as target:
            for chunk in iter(lambda: target.read(BLOCK_SIZE), b''):
                pass

これは実際にはwhile 1:(マイナーなコードの臭いと見なされることもあります) とif not data:チェックを削除するだけです。withを使用すると、これが Python 2.7+に制限されることにも注意してください。

于 2015-08-31T13:48:41.553 に答える
2

zero.tar.gz を手動で破損しようとしました (16 進エディタ - 数バイトの中間ファイルを編集)。最初の例外は IOError をキャッチしません...

トレースバックを見ると、 を呼び出したときにスローされていることがわかるtardude.getmembers()ので、次のようなものが必要になります...

try:
    tardude = tarfile.open("zero.tar.gz")
except:
    print "There was an error opening tarfile. The file might be corrupt or missing."

try:
    members = tardude.getmembers()
except:
    print "There was an error reading tarfile members."

for member_info in members:
    try:
        check = tardude.extractfile(member_info.name)
    except:
        print "File: %r is corrupt." % member_info.name

tardude.close()

元の問題に関しては、あなたはほとんどそこにいます。check次のような方法でオブジェクトからデータを読み取るだけです...

BLOCK_SIZE = 1024

try:
    tardude = tarfile.open("zero.tar.gz")
except:
    print "There was an error opening tarfile. The file might be corrupt or missing."

try:
    members = tardude.getmembers()
except:
    print "There was an error reading tarfile members."

for member_info in members:
    try:            
        check = tardude.extractfile(member_info.name)
        while 1:
            data = check.read(BLOCK_SIZE)
            if not data:
                break
    except:
        print "File: %r is corrupt." % member_info.name

tardude.close()

BLOCK_SIZE...これにより、一度にバイトを超えるメモリを使用しないようにする必要があります。

また、使用を避けるようにしてください...

try:
    do_something()
except:
    do_something_else()

...予期しない例外をマスクするためです。実際に処理するつもりの例外のみをキャッチするようにしてください...

try:
    do_something()
except IOError:
    do_something_else()

...そうしないと、コードのバグを検出するのが難しくなります。

于 2013-04-15T11:23:40.460 に答える
1

モジュールを使用してファイルsubprocessを呼び出すことができgzip -tます...

from subprocess import call
import os

with open(os.devnull, 'w') as bb:
    result = call(['gzip', '-t', "zero.tar.gz"], stdout=bb, stderr=bb)

が 0 でない場合result、何かが間違っています。ただし、gzip が利用可能かどうかを確認することをお勧めします。そのためのユーティリティ関数を作成しました。

import subprocess
import sys
import os

def checkfor(args, rv = 0):
    """Make sure that a program necessary for using this script is
    available.

    Arguments:
    args  -- string or list of strings of commands. A single string may
             not contain spaces.
    rv    -- expected return value from evoking the command.
    """
    if isinstance(args, str):
        if ' ' in args:
            raise ValueError('no spaces in single command allowed')
        args = [args]
    try:
        with open(os.devnull, 'w') as bb:
            rc = subprocess.call(args, stdout=bb, stderr=bb)
        if rc != rv:
            raise OSError
    except OSError as oops:
        outs = "Required program '{}' not found: {}."
        print(outs.format(args[0], oops.strerror))
        sys.exit(1)
于 2013-04-15T11:21:52.140 に答える