如何在不包含父目录的 Python/tar 中创建 tar.gz 存档?

Posted

技术标签:

【中文标题】如何在不包含父目录的 Python/tar 中创建 tar.gz 存档?【英文标题】:How to create tar.gz archive in Python/tar without include parent directory? 【发布时间】:2019-10-16 21:50:50 【问题描述】:

我有一个包含 FolderB 和 FileB 的 FolderA。如何创建仅包含 FolderB 和 FileB 的 tar.gz 存档,删除父目录 FolderA?我正在使用 Python,并且正在 Windows 机器上运行此代码。

我找到的最佳线索是:How to create full compressed tar file using Python?

在最受好评的答案中,人们讨论了删除父目录的方法,但它们都不适合我。我试过 arcname、os.walk,并通过 subprocess.call() 运行 tar 命令。

我接近了 os.walk,但在下面的代码中,它仍然在 FolderB 和 FileB 中放置了一个“_”目录。所以,文件结构是ARCHIVE.tar.gz > ARCHIVE.tar > "_" 目录,FolderB,FileB。

def make_tarfile(output_filename, source_dir):
    with tarfile.open(output_filename, "w:gz") as tar:
        length = len(source_dir)
        for root, dirs, files in os.walk(source_dir):
            folder = root[length:]  # path without "parent"
            for file in files:
                tar.add(os.path.join(root, folder), folder)

我使用以下方法制作存档:

make_tarfile('ARCHIVE.tar.gz', 'C:\FolderA')

我应该继续使用os.walk,还是有其他方法可以解决这个问题?

更新

Here is an image showing the contents of my archive. 如您所见,我的存档中有一个“_”文件夹,我想删除它——奇怪的是,当我提取时,只有 FolderA 和 FileB.html 显示为存档。本质上,这种行为是正确的,但如果我可以从存档中删除“_”文件夹的最后一步,那将是完美的。我将提出一个更新的问题以减少混淆。

【问题讨论】:

你为什么不能只是tar.extractall(path=destination),其中tar来自tarfile.open(FolderB_path) 如果我是这个 .tar.gz 的唯一消费者,我当然可以这样做,但我正在创建一个 .tar.gz 供其他人使用,它需要具有特定的结构。 【参考方案1】:

这对我有用:

with tarfile.open(output_filename, "w:gz") as tar:
    for fn in os.listdir(source_dir):
        p = os.path.join(source_dir, fn)
        tar.add(p, arcname=fn)

即只需列出源目录的根目录并将每个条目添加到存档中。无需遍历源目录,因为通过 tar.add() 添加目录是自动递归的。

【讨论】:

【参考方案2】:

我试图提供一些示例,说明对源目录的更改如何影响最终提取的内容。

按照你的例子,我有这个文件夹结构

我有这个 python 来生成 tar 文件(来自here)

import tarfile
import os

def make_tarfile(output_filename, source_dir):
    with tarfile.open(output_filename, "w:gz") as tar:
        tar.add(source_dir, arcname=os.path.basename(source_dir))

tar 文件中包含哪些数据和结构取决于我作为参数提供的位置。

所以这个位置参数,

make_tarfile('folder.tar.gz','folder_A/' )

提取时会生成这个结果

如果我移动到文件夹_A 并参考文件夹_B,

make_tarfile('folder.tar.gz','folder_A/folder_B' )

这就是提取的内容,

请注意,folder_B 是此提取的根目录。

现在终于,

make_tarfile('folder.tar.gz','folder_A/folder_B/' )

将提取到这个

只有文件包含在提取中。

【讨论】:

感谢您的回复。关键是(如果我在传达这一点上做得不好,很抱歉),FolderB 和 FileB 位于同一目录级别,它们都直接位于 C:\FolderA 中。因此,您的第一个示例将提取 FolderA 及其内容;您的第二个示例将提取 FolderB 及其内容;但我希望 FolderA 的内容根本不出现 FolderA。我曾尝试使用 arcname=os.path.basename(source_dir) 但始终包含 FolderA。【参考方案3】:

这是执行任务的函数。我在 Windows(使用 WinRar)上提取 tar 时遇到了一些问题,因为它似乎尝试两次提取同一个文件,但我认为正确提取存档时它会正常工作。

"""
The directory structure I have is as follows:

├───FolderA
│   │   FileB
│   │
│   └───FolderB
│           FileC
"""

import tarfile
import os

# This is where I stored FolderA on my computer
ROOT = os.path.join(os.path.dirname(__file__), "FolderA")


def make_tarfile(output_filename: str, source_dir: str) -> bool:
    """ 
    :return: True on success, False otherwise
    """

    # This is where the path to each file and folder will be saved
    paths_to_tar = set()

    # os.walk over the root folder ("FolderA") - note it will never get added
    for dirpath, dirnames, filenames in os.walk(source_dir):

        # Resolve path issues, for example for Windows
        dirpath = os.path.normpath(dirpath)

        # Add each folder and path in the current directory
        # Probably could use zip here instead of set unions but can't be bothered to try to figure it out
        paths_to_tar = paths_to_tar.union(
            os.path.join(dirpath, d) for d in dirnames).union(
            os.path.join(dirpath, f) for f in filenames)

    try:
        # This will create the tar file in the current directory
        with tarfile.open(output_filename, "w:gz") as tar:

            # Change the directory to treat all paths relatively
            os.chdir(source_dir)

            # Finally add each path using the relative path
            for path in paths_to_tar:
                tar.add(os.path.relpath(path, source_dir))
            return True

    except (tarfile.TarError, OSError) as e:
        print(f"An error occurred - e")
        return False


if __name__ == '__main__':
    make_tarfile("tarred_files.tar.gz", ROOT)

【讨论】:

嗨,Kacper,感谢您的回复!我能够使用 7zip CLI 来实现我所需要的,但我肯定也想抽出时间尝试一下您的解决方案。我在另一个问题中发布了我的发现:***.com/questions/58423574/…【参考方案4】:

您可以使用subprocess 来实现类似但much faster。

def make_tarfile(output_filename, source_dir):
    subprocess.call(["tar", "-C", source_dir, "-zcvf", output_filename, "."])

【讨论】:

以上是关于如何在不包含父目录的 Python/tar 中创建 tar.gz 存档?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不传递值和 rowid 的情况下在 H base-shell 中创建表?

如何在不插入值的情况下在sql中创建动态行?

在不同类的构造函数中创建对象的动态数组

如何在不使用 boost 的情况下创建目录?

无法写入文件 pri:无法在 QT mac os 中创建父目录

如何扩展mercurial存储库以包含父文件夹?