如何使用Python创建完整的压缩tar文件?

178

我该如何使用Python创建一个带有压缩的.tar.gz文件?


27
tar 不会压缩数据,它只是将文件打包在一起。实际的压缩工作由 gzip 完成。 - Ignacio Vazquez-Abrams
10个回答

304

为整个目录树构建一个.tar.gz(也称为.tgz)的方法:

import tarfile
import os.path

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档案,其中包含一个顶层文件夹,其名称和内容与source_dir相同。


54
读者需要注意,如果省略 arcname=os.path.basename(source_dir) 这部分代码,将会在 tar 文件中保留整个 source_dir 的路径结构(在大多数情况下,这会造成不便)。 - Brōtsyorfuzthrāx
33
第二点提示:使用 arcname=os.path.basename(source_dir) 仍然意味着归档文件包含一个文件夹,其中包含 source_dir 的内容。如果你想要归档文件的根目录包含这些内容本身,而不是一个文件夹内的内容,请使用 arcname=os.path.sep - Jonathan H
7
很抱歉,@Sheljohn的说法并不完全正确。因为如果使用os.path.sep,归档文件将包含服务“。”或“/”文件夹,通常不是问题,但有时在以后编程处理此归档文件时可能会出现问题。似乎唯一真正干净的方式是使用os.walk并逐个添加文件。 - The Godfather
9
要摆脱所有目录结构,只需使用arcname='.'。不需要使用os.walk - edthrn
如果我在Linux上生成这个tar文件,它能在其他平台上成功打开,比如Windows和Mac吗? - jrp
1
@jrp 在Linux上创建它并不重要,重要的是你选择(或不选择)什么格式;在Python 3.8之前的默认格式是“GNU_FORMAT”,可能不被所有工具读取,但截至Python 3.8的默认格式是“PAX_FORMAT”,这是一种标准格式,也是旧有标准USTAR_FORMAT的保守/兼容扩展,被广泛支持。 - kbolino

113
import tarfile
tar = tarfile.open("sample.tar.gz", "w:gz")
for name in ["file1", "file2", "file3"]:
    tar.add(name)
tar.close()
如果你想创建一个tar.bz2压缩文件,只需要将文件扩展名替换为".tar.bz2",并将"w:gz"替换为"w:bz2"。

26
在Python中,你应该真正使用with tarfile.open(…),而不是手动调用openclose。这也适用于打开普通文件。 - Jonathan H
我只想压缩成sample.gz。import tarfile tar = tarfile.open("sample.gz", "r:gz") for name in ["file1", "file2", "file3"]: tar.add(name) tar.close()可以吗? - thach.nv92

35

你可以使用 tarfile.open 方法并设置 mode='w:gz' 来进行 gzip 压缩写入操作。

通常建议将文件名(传递给 open 方法的 name 参数)以 .tar.gz 结尾,但这不会影响压缩效果。

顺便说一下,设置为 'w:bz2' 模式时通常可以获得更好的压缩效果。就像通过 bzip2 而不是 gzip 进行压缩时,tar 通常也能够获得更好的压缩率。


8
请注意,bzip2压缩的tarball文件名应以“.tar.bz2”结尾。 - Ignacio Vazquez-Abrams

25

之前的答案建议使用Python中的tarfile模块创建.tar.gz文件。显然,这是一种好的Python风格的解决方案,但它在打包速度上有明显的缺陷。 这个问题 提到,tarfile比Linux中的tar工具慢大约两倍。根据我的经验,这个估计相当正确。

因此,为了更快地打包,您可以使用subprocess模块中的tar命令:

subprocess.call(['tar', '-czf', output_filename, file_to_archive])

为了在多核机器上对非常大的tar压缩包获得显著的加速,您可以调用外部并行压缩程序,例如pigzlbzip2subprocess.check_call(("tar", "-I", "lbzip2 --fast", "-cf", output_filename, file_to_archive)) - George V. Reilly

7

shutil.make_archive 对于文件和目录(递归添加到档案中)非常方便:

import shutil

compressed_file = shutil.make_archive(
        base_name='archive',   # archive file name w/o extension
        format='gztar',        # available formats: zip, gztar, bztar, xztar, tar
        root_dir='path/to/dir' # directory to compress
)

3

除了 @Aleksandr Tukallo的回答之外,您还可以获取输出和错误消息(如果有发生)。使用 tar 压缩文件夹的方法在以下回答中解释得相当清楚。

import traceback
import subprocess

try:
    cmd = ['tar', 'czfj', output_filename, file_to_archive]
    output = subprocess.check_output(cmd).decode("utf-8").strip() 
    print(output)          
except Exception:       
    print(f"E: {traceback.format_exc()}")       

2

在这个tar.gz文件中,压缩了一个公开可见的目录。在解决方案中使用os.path.basename(file_directory)。

import tarfile

with tarfile.open("save.tar.gz","w:gz") as tar:
      for file in ["a.txt","b.log","c.png"]:
           tar.add(os.path.basename(file))

在目录中压缩tar.gz文件的使用


导入 tarfile 包 - THAVASI.T

0

对 @THAVASI.T 的答案进行了小修正,补充了 'tarfile' 库的导入,并定义了第三行中使用的 'tar' 对象。

import tarfile

with tarfile.open("save.tar.gz","w:gz") as tar:
    for file in ["a.txt","b.log","c.png"]:
        tar.add(os.path.basename(file))

你应该考虑扩展这个答案,包括详细说明其他答案的问题所在,并解释为什么这段代码有效。 - Alex Reinking

-1

只是在更清晰的形式下重新阐述@George V. Reilly的出色回答...

import tarfile


fd_path="/some/folder/path/"
fl_name="some_file_name.ext"
targz_fd_path_n_fl_name="/some/folder/path/some_file_name.tar.gz"

with tarfile.open(targz_fd_path_n_fl_name, "w:gz") as tar:
    tar.add(fd_path + fl_name, fl_name)

如 @Brōtsyorfuzthrāx 所指出的(但是以另一种方式),如果您忽略 "add" 方法的第二个参数,那么它将为您提供 tar 文件中 fd_path + fl_name 的完整路径结构。
当然,您可以使用...
import tarfile
import os

fd_path_n_fl_name="/some/folder/path/some_file_name.ext"
targz_fd_path_n_fl_name="/some/folder/path/some_file_name.tar.gz"

with tarfile.open(targz_fd_path_n_fl_name, "w:gz") as tar:
    tar.add(fd_path_n_fl_name, os.path.basename(fd_path_n_fl_name))

如果您不想使用或没有文件夹路径和文件名分开,可以这样做。

谢谢!


-5

在压缩文件中实现最佳性能,且不包含 ...请见下方漏洞警告:

注意(感谢MaxTruxa):

此答案易受shell注入攻击。请阅读文档中的安全注意事项。如果使用shell=True,永远不要传递未经转义的字符串给subprocess.runsubprocess.call等函数。仅对Unix shell使用shlex.quote进行转义。

我正在本地使用它,所以对我的需求很好。

subprocess.call(f'tar -cvzf {output_filename} *', cwd=source_dir, shell=True)

cwd参数在压缩之前更改目录 - 这解决了点的问题。

shell=True允许使用通配符(*

也适用于递归目录


3
“完美答案”容易受到Shell注入攻击。请参阅文档中的安全注意事项。如果使用shell=True,不要将未转义的字符串传递给subprocess.runsubprocess.call等函数。请使用shlex.quote进行转义(仅适用于Unix Shell)。 - Max Truxa
感谢@MaxTruxa提供的重要信息。 - Yitzchak
我一直因为这个回答而被踩,但是我不能删除它,因为让它完美运行非常困难,并且对于本地使用(而不是部署的脚本)来说是100%安全的。我真的相信它将在未来帮助我! - Yitzchak
我希望你能够减小字体大小。因为它不是一个完美的答案。 - Keiku
@Keiku,好的,我明白了。现在更好了吗? - Yitzchak
@Yitzchak 谢谢你的回复。当我得到负面评价时,我的心情也很受伤。让我们尽力吧。 - Keiku

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