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

Compressing directory using shutil.make_archive() while preserving directory structure

我正在尝试使用以下代码将名为 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

使用文档中的术语,您指定了 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)

参考:

我自己写了一个包装函数,因为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')

我认为,我可以通过删除移动文件来改进 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)

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

;TLDR。 运行 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.

假设你有 ~/temp/dir1:

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

如何创建 dir1 的存档?设置 base_name='dir1_arc'format='zip'。那么你有很多的选择:

  • cd变成dir1和运行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,您可以将其解压缩到 dir1root_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。我们可以继续提问。

我在使用“.”分割某些路径时遇到问题他们中的句点,我发现有一个默认为 '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 的原始回答,否则我会在酱汁中迷路更长时间。

此解决方案基于 irudyak 和 seanbehan 的响应并使用 Pathlib。您需要将 sourcedestination 作为路径对象传递。

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)

这是@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"))