如何将文件从 python 中的 tarfile 提取到不同的目标文件名?

问题描述

我有一个 tarfile.TarFile,我想从中提取一些文件修改后的目标文件名;有一个与我不想触及的存档成员同名的现有文件。 具体来说,我想附加一个后缀,例如存档中名为 foo/bar.txt 的成员应提取foo/bar.txt.mysuffix

这两个有些明显但也有些不尽人意的方法是:

  • 使用extractfile提取每个文件,使用shutil.copyfileobj创建重命名文件并复制内容;但是,这要么仅限于常规文件,要么仅限于所有特殊处理,例如,必须复制 tarfile 中实现的稀疏文件、符号链接、目录等。
  • extractall一个临时目录,然后重命名并复制到目的地;这只是感觉不必要的复杂,需要与主机系统进行更多交互并引入新的故障模式,而且似乎很容易犯这种微妙的错误(例如,请参阅 shutil.copy/copy2 上的警告)。

TarFile 上是否没有接口或钩子可以简洁、正确地实现这一点?

解决方法

有 TarFile.getmembers() 方法,它以列表的形式返回档案的成员。 在那里您可以循环并选择要提取或不提取的文件。根据 tar 的大小,您的第二种方法也可行,但不是最好的。

object = tarfile.open('example.tar','r')
for member in object.getmembers():
    if "whatever" in member.name:
        object.extract(member,"example_dir")
,

浏览Lib/tarfile.py,我发现了这个comment

    #--------------------------------------------------------------------------
    # Below are the different file methods. They are called via
    # _extract_member() when extract() is called. They can be replaced in a
    # subclass to implement other functionality.

    def makedir(self,tarinfo,targetpath):
       #...
    
    def makefile(self,targetpath):
       # ...

官方参考文档中没有提到这些方法,但它们似乎是公平的游戏。要在现有的打开 TarFile 实例上覆盖这些,我们可以创建一个子类 Facade/Wrapper:

class SuffixingTarFile(tarfile.TarFile):
    def __init__(self,suffix: str,wrapped: tarfile.TarFile):
        self.suffix = suffix
        self.wrapped = wrapped

    def __getattr__(self,attr):
        return getattr(self.wrapped,attr)

    def makefile(self,targetpath):
        super().makefile(tarinfo,targetpath + self.suffix)

    # overwrite makedir,makelink,makefifo,etc. as desired

示例:

tar = tarfile.open(...)
star = SuffixingTarFile(".foo",tar)
star.extractall()  # extracts all (regular) file members with .foo suffix appended