使用Python检查tar文件的完整性。

3
我正在将我的备份脚本从shell转换到Python。 我的旧脚本的一个功能是通过执行命令"gzip -t"检查创建的tar文件的完整性。 但在Python中似乎有点棘手。
看起来唯一的方法是逐个读取tar文件中压缩的TarInfo对象,然后检查其完整性。 是否有一种方法可以在不将其提取到磁盘或完整保留在内存中的情况下检查tar文件的完整性?
freenode上#python的好心人建议我应该逐块读取每个TarInfo对象并丢弃已读取的每个块。 但我承认,由于我刚开始学习Python,我不知道如何做到这一点。
想象一下,我有一个30GB的tar文件,其中包含大小从1kb到10GB的文件...这是我开始编写的解决方案:
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()

这段代码还远未完成。如果我在一个大约30GB的tar归档文件上运行它,我不敢保证check对象不会超过10GB以上(如果tar归档文件中有这么大的文件的话)。

额外说明: 我尝试手动损坏zero.tar.gz文件(使用十六进制编辑器在文件中间编辑几个字节)。第一个异常没有捕获到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

我已经尝试过tarfile模块来处理大量文件,问题在于tarfile.TarFile模块会将所有读取(或写入)的成员存储在它的"members"中。所以当你有意读取一个拥有大量小文件的tarbomb时,它会占用大量的内存空间。 - tdihp
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+ 中运行。


2

我尝试手动损坏 zero.tar.gz 文件(使用十六进制编辑器,编辑文件中的几个字节)。但是第一个异常没有捕获到 IOError 异常...

如果你查看回溯信息(traceback),你会发现它是在调用 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()

否则,您将会发现在代码中检测错误变得更加困难。

太好了!关于“except:”的东西...我知道那个...我通常会写“except this:”、“except that:”...“except:”,但这只是为了测试:D - Kaurin
我已完成以下操作:http://pastie.org/7585277。正如您所见,有一个检查member_info.isfile的步骤,因为解析目录总是会出现错误。我还希望跳过解析除纯文件以外的其他任何内容。 - Kaurin
1
你需要在for循环中检查member_info对象。类似于if not member_info.isfile(): continue的语句应该可以工作。 - Aya
这有点帮助,但也有一些问题(由于“Except:”和“tarfile”问题,请将它们从上面的代码示例中删除):我仍然需要传递错误的tar文件来通过此测试 - 确保tar文件正确的唯一方法是调用tardude.extractall("/some/tmp/dir") - Badmaster

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)

如果result不为0,则表示出现了问题。你可能想检查一下是否可用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)

抱歉,我忘了提到我想使用Pythonic方法,而不是使用子进程。不过还是谢谢你的回答! - Kaurin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接