代码之家  ›  专栏  ›  技术社区  ›  Kaurin

在Python中检查tarfile的完整性

  •  3
  • Kaurin  · 技术社区  · 13 年前

    我正在将备份脚本从shell转换为Python。我的旧脚本的一个功能是通过执行:gzip-t来检查创建的tarfile的完整性。

    这在Python中似乎有点棘手。

    似乎唯一的方法是读取tarfile中的每个压缩的TarInfo对象。

    有没有一种方法可以检查tarfile的完整性,而无需将其提取到磁盘,或将其保存在内存中(整体)?

    freenode上#python上的好人建议我应该逐块读取每个TarInfo对象,丢弃读取的每个块。

    我必须承认,我不知道如何做到这一点,因为我刚刚开始使用Python。

    想象一下,我有一个30GB的tarfile,其中包含从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将是一个10+GB的对象(如果我在tar存档中有这么大的文件)

    奖金: 我尝试手动损坏zero.tar.gz(十六进制编辑器-中间编辑几个字节)。第一个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
    
    3 回复  |  直到 13 年前
        1
  •  3
  •   Community Mohan Dere    9 年前

    只是在 Aya's 答案使事情变得更习惯(尽管我删除了一些错误检查,以使机制更明显):

    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
  •  2
  •   Aya    13 年前

    我尝试手动损坏zero.tar.gz(十六进制编辑器-编辑几个字节 中足)。第一个except没有捕获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()
    

    …否则你会发现在你的代码中检测错误更加困难。

        3
  •  1
  •   Roland Smith    13 年前

    您可以使用 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)
    
    推荐文章