使用shutil.make_archive()压缩目录并保留目录结构

68

我正在尝试使用以下代码将名为test_dicoms的目录压缩到名为test_dicoms.zip的zip文件中:

shutil.make_archive('/home/code/test_dicoms', 'zip', '/home/code/test_dicoms')
当我解压缩时,问题是所有在/test_dicoms/中的文件都被提取到/home/code/而不是文件夹/test_dicoms/中,并且其中包含的所有文件都被提取到/home/code/。因此,在/test_dicoms/中有一个名为foo.txt的文件,但在我对其进行压缩和解压缩后,foo.txt的路径为/home/code/foo.txt,而不是/home/code/test_dicoms/foo.txt。我该如何解决这个问题?此外,我正在处理的一些目录非常大。我是否需要在我的代码中添加任何内容以使它成为ZIP64,或者该函数是否足够聪明以自动完成?以下是当前存档中的内容:
[gwarner@jazz gwarner]$ unzip -l test_dicoms.zip
Archive: test_dicoms.zip
Length    Date       Time  Name
--------- ---------- ----- ----
    93324 09-17-2015 16:05 AAscout_b_000070
    93332 09-17-2015 16:05 AAscout_b_000125
    93332 09-17-2015 16:05 AAscout_b_000248

请提供zip目录的复制粘贴。例如,如果您使用Linux,请运行“unzip -l test_dicoms.zip”。 - Robᵩ
`[gwarner@jazz gwarner]$ unzip -l test_dicoms.zipArchive: test_dicoms.zip Length Date Time Name
93324 09-17-2015 16:05 AAscout_b_000070 93332 09-17-2015 16:05 AAscout_b_000125 93332 09-17-2015 16:05 AAscout_b_000248`
- G Warner
8个回答

79

在文档中使用的术语,您已经指定了root_dir,但没有指定base_dir。尝试这样指定base_dir

shutil.make_archive('/home/code/test_dicoms',
                    'zip',
                    '/home/code/',
                    'test_dicoms')

回答你的第二个问题,这取决于你使用的 Python 版本。从 Python 3.4 开始,ZIP64 扩展将默认可用。在 Python 3.4 之前,make_archive 不会自动创建带有 ZIP64 扩展名的文件。如果你正在使用较旧版本的 Python,并且想要 ZIP64,可以直接调用底层的 zipfile.ZipFile()

如果你选择直接使用 zipfile.ZipFile(),而不是通过 shutil.make_archive(),以下是一个示例:

import zipfile
import os

d = '/home/code/test_dicoms'

os.chdir(os.path.dirname(d))
with zipfile.ZipFile(d + '.zip',
                     "w",
                     zipfile.ZIP_DEFLATED,
                     allowZip64=True) as zf:
    for root, _, filenames in os.walk(os.path.basename(d)):
        for name in filenames:
            name = os.path.join(root, name)
            name = os.path.normpath(name)
            zf.write(name, name)

Reference:


1
请说明您所说的“直接调用底层的zipfile.ZipFile()”是什么意思? - martineau
请注意,Python 3.4+默认支持shutil压缩zip64功能:http://bugs.python.org/issue17189 - danqing
但是 ZipFile 不支持 gzip。 - ThisGuyCantEven

26

由于shutil.make_archive使用起来过于复杂,我自己编写了一个包装函数。

这是它的代码:http://www.seanbehan.com/how-to-use-python-shutil-make_archive-to-zip-up-a-directory-recursively-including-the-root-folder/

仅展示代码:

import os, shutil
def make_archive(source, destination):
        base = os.path.basename(destination)
        name = base.split('.')[0]
        format = base.split('.')[1]
        archive_from = os.path.dirname(source)
        archive_to = os.path.basename(source.strip(os.sep))
        shutil.make_archive(name, format, archive_from, archive_to)
        shutil.move('%s.%s'%(name,format), destination)

make_archive('/path/to/folder', '/path/to/folder.zip')

你有没有对这个函数进行过任何时间/压缩测试,以比较使用os.walk()zipfile的效果?你的函数很棒,但它破坏了shutil自己的make_archive()的简单性 - 这不是你的错,只是shutil的一个缺点。 - elPastor
不,我没有进行任何基准测试。我只是发现make_archive()非常难以使用,从来无法记住参数的正确顺序。在我看来,只需要输入文件夹和输出文件就足够了。 - seanbehan
1
你能帮我检查一下我的答案,看看这个是否在不移动文件的情况下做了相同的事情吗? - Make42

5

使用shutil有两种基本方法:你可以尝试理解其背后的逻辑,也可以直接使用示例。我在这里找不到一个示例,所以我尝试创建了自己的示例。

;简而言之,请从temp运行 shutil.make_archive('dir1_arc', 'zip', root_dir='dir1') 或者 shutil.make_archive('dir1_arc', 'zip', base_dir='dir1') 或者只需要shutil.make_archive('dir1_arc', 'zip', 'dir1')

假设您有~/temp/dir1

temp $ tree dir1
dir1
├── dir11
│   ├── file11
│   ├── file12
│   └── file13
├── dir1_arc.zip
├── file1
├── file2
└── file3

如何创建 dir1 的归档文件?将 base_name='dir1_arc'format='zip'。你有很多选择:
  • cddir1 并运行 shutil.make_archive(base_name=base_name, format=format);它会在 dir1 内创建一个归档文件 dir1_arc.zip;唯一的问题是,在归档文件中,你会找到文件 dir1_arc.zip
  • temp 运行 shutil.make_archive(base_name=base_name, format=format, base_dir='dir1');你会在 temp 中得到 dir1_arc.zip,你可以将其解压缩到 dir1 中;root_dir 默认为 temp
  • ~ 运行 shutil.make_archive(base_name=base_name, format=format, root_dir='temp', base_dir='dir1');这一次,你将再次得到你的文件,但是这次是在 ~ 目录下;
  • ~ 中创建另一个目录 temp2 并在其中运行:shutil.make_archive(base_name=base_name, format=format, root_dir='../temp', base_dir='dir1');你将在此 temp2 文件夹中得到你的归档文件;

你可以不指定参数运行 shutil 吗?可以。从 temp 运行 shutil.make_archive('dir1_arc', 'zip', 'dir1')。这与运行 shutil.make_archive('dir1_arc', 'zip', root_dir='dir1') 是相同的。在这种情况下,我们对于 base_dir 可以说什么?从文档中看不出很多信息。从源代码中,我们可以看到:

if root_dir is not None:
  os.chdir(root_dir)

if base_dir is None:
        base_dir = os.curdir 

所以在我们的案例中,base_dirdir1。我们可以继续提问。

5

我认为可以通过取消文件移动的方式改进seanbehan的答案:

def make_archive(source, destination):
    base_name = '.'.join(destination.split('.')[:-1])
    format = destination.split('.')[-1]
    root_dir = os.path.dirname(source)
    base_dir = os.path.basename(source.strip(os.sep))
    shutil.make_archive(base_name, format, root_dir, base_dir)

4
我在一些路径中使用'.'时出现了路径分割问题,我发现添加一个可选格式(默认为'zip')很方便,而且仍然允许您覆盖其他格式,并且更少出错。
import os
import shutil
from shutil import make_archive

def make_archive(source, destination, format='zip'):
    import os
    import shutil
    from shutil import make_archive
    base, name = os.path.split(destination)
    archive_from = os.path.dirname(source)
    archive_to = os.path.basename(source.strip(os.sep))
    print(f'Source: {source}\nDestination: {destination}\nArchive From: {archive_from}\nArchive To: {archive_to}\n')
    shutil.make_archive(name, format, archive_from, archive_to)
    shutil.move('%s.%s' % (name, format), destination)


make_archive('/path/to/folder', '/path/to/folder.zip')

非常感谢seanbehan原始回答,否则我会在这里浪费更长时间。


1
这是对@nick答案的改进版本,使用了pathlib、类型提示,并避免了内置名称重名:
from pathlib import Path
import shutil

def make_archive(source: Path, destination: Path) -> None:
    base_name = destination.parent / destination.stem
    fmt = destination.suffix.replace(".", "")
    root_dir = source.parent
    base_dir = source.name
    shutil.make_archive(str(base_name), fmt, root_dir, base_dir)

使用方法:

make_archive(Path("/path/to/dir/"), Path("/path/to/output.zip"))

1
你可以使用 Pathlibshutil:
from pathlib import Path
import shutil
shutil.make_archive(
   *dest_path.split('.'), 
   root_dir=Path(src_path).parent, 
   base_dir=Path(src_path).name)
)
  • src_path 是源目录的路径。
  • dest_path 是要创建的目标归档文件的路径。

0

这个解决方案基于 irudyak 和 seanbehan 的回答,并使用了 Pathlib。你需要将 sourcedestination 作为 Path 对象传递。

from pathlib import Path
import shutil

def make_archive(source, destination):
    base_name = destination.parent / destination.stem
    format = (destination.suffix).replace(".", "")
    root_dir = source.parent
    base_dir = source.name
    shutil.make_archive(base_name, format, root_dir, base_dir)

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