【问题标题】:Compressing directory using shutil.make_archive() while preserving directory structure使用 shutil.make_archive() 压缩目录,同时保留目录结构
【发布时间】:2015-09-17 21:11:41
【问题描述】:

我正在尝试使用以下代码将名为 test_dicoms 的目录压缩到名为 test_dicoms.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
  • [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


【解决方案1】:

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

参考:

【讨论】:

  • 请澄清您的意思是“直接调用底层zipfile.ZipFile()”?
  • 注意python 3.4+默认支持zip64的shutil bugs.python.org/issue17189
【解决方案2】:

我在某些带有 '.' 的路径上遇到了路径拆分问题。其中的句点,我发现有一个默认为“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的原始答案,否则我会在酱汁中迷失更长的时间。

【讨论】:

    【解决方案3】:

    我自己编写了一个包装函数,因为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的一个缺点。
    • 不,我没有进行任何基准测试。我刚刚发现make_archive() 很难使用.. 永远无法记住 args 的正确顺序。 imo 似乎只需要输入文件夹和输出文件。
    • 你能检查我的答案,看看这是否在不移动文件的情况下做同样的事情?
    【解决方案4】:

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

    ;TLDR。从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'。那么你有很多的选择:

    • 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 吗?你可以。从tempshutil.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)
      

      【讨论】:

        【解决方案6】:

        此解决方案建立在 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)
        

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-03-16
            • 1970-01-01
            • 2021-12-29
            • 2011-09-07
            • 1970-01-01
            • 2020-12-18
            • 2021-01-15
            • 1970-01-01
            相关资源
            最近更新 更多