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

问题描述 投票:0回答:9

我正在尝试使用以下代码将名为

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
python directory zip shutil
9个回答
82
投票

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

参考:


30
投票

我自己写了一个包装函数,因为

shutil.make_archive
使用起来太混乱了。

这里是http://www.seanbehan.com/how-to-use-python-shutil-make_archive-to-zip-up-a-directory-recursively-include-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')

7
投票

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

5
投票

基本上有两种使用

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_arc.zip
    内创建一个存档
    dir1
    ;唯一的问题是你会得到一个奇怪的行为:在你的档案中你会发现文件
    dir1_arc.zip
    ;
  • temp
    运行
    shutil.make_archive(base_name=base_name, format=format, base_dir='dir1')
    ;你会在
    dir1_arc.zip
    中得到
    temp
    ,你可以将其解压到
    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_dir
dir1
。我们可以继续提问。


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
投票

您可以使用

Pathlib
shutil
:

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
。您需要将
source
destination
作为 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)

0
投票

意识到答案是旧的/使用旧的 python 方法。 有新的 python 路径处理方法,但不幸的是,

shutil.make_archive
仍然令人困惑。

请参阅此解决方案以了解现代方式的 python 方法。

def compress_directory_in_winos(
    src_dirpath: Path, dst_fpath: Path, fileformat: str = "zip"
):
    archive_name = src_dirpath.name
    root_dir = src_dirpath.parent.absolute()
    base_dir = src_dirpath.absolute().name

    output_str = shutil.make_archive(
        archive_name,
        fileformat,
        root_dir=root_dir,
        base_dir=base_dir,
    )

    # it is difficult to control shutil.make_archive how
    # and where the output will be;
    # but we can easily move it to your dst_fpath
    outpath = Path(output_str)
    outpath.rename(dst_fpath)
© www.soinside.com 2019 - 2024. All rights reserved.