如何使用 Python 创建文件路径的 zip 文件,包括空目录?

Posted

技术标签:

【中文标题】如何使用 Python 创建文件路径的 zip 文件,包括空目录?【英文标题】:How do I create a zip file of a file path using Python, including empty directories? 【发布时间】:2012-08-01 02:33:23 【问题描述】:

我一直在尝试使用zipfileshutil.make_archive 模块递归地创建目录的zip 文件。两个模块都工作得很好——除了空目录不会被添加到存档中。包含其他空目录的空目录也会被静默跳过。

我可以使用 7Zip 创建相同路径的存档并保留空目录。因此我知道这在文件格式本身是可能的。我只是不知道如何在 Python 中做到这一点。有任何想法吗?谢谢!

【问题讨论】:

负责人代码是here。 尝试在目录中添加一个虚拟文件,然后从存档中删除该文件,以查看 zipfile 是否真的支持空目录(尽管格式支持,但某些 zip 实现不支持)。跨度> 【参考方案1】:

有一个使用zipfile的例子:

import os, zipfile  
from os.path import join  
def zipfolder(foldername, filename, includeEmptyDIr=True):   
    empty_dirs = []  
    zip = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)  
    for root, dirs, files in os.walk(foldername):  
        empty_dirs.extend([dir for dir in dirs if os.listdir(join(root, dir)) == []])  
        for name in files:  
            zip.write(join(root ,name))  
        if includeEmptyDIr:  
            for dir in empty_dirs:  
                zif = zipfile.ZipInfo(join(root, dir) + "/")  
                zip.writestr(zif, "")  
        empty_dirs = []  
    zip.close() 

if __name__ == "__main__":
    zipfolder('test1/noname/', 'zip.zip')

【讨论】:

您的代码将绝对路径保存在存档中。我稍微修改了它以保存相对路径(因为它将在另一台机器上解压缩)并且它工作得很好!感谢您的帮助! 是的,我也这样做了,但这很容易。必须将项目向后移植到 2.6,所以我不得不替换 shutil.make_archive() 调用。 在 Python 2.7.3 下测试,它不会压缩空目录,但会压缩其他所有内容。 empty_dirs.extend([dir for dir in dirs if os.listdir(join(root, dir)) == []]) 也发生了很多事情 - 它只需要为每个根目录触发,而不是根目录中的每个目录(因为将进入空目录) 我认为将 ZipFile 对象命名为“zip”是不好的做法,因为它会覆盖 python 中的内置 zip 函数。【参考方案2】:

您需要register a new archive format 来执行此操作,因为默认的 ZIP 存档器不支持此操作。看看the meat of the existing ZIP archiver。使用当前未使用的 dirpath 变量创建您自己的存档器。我寻找如何创建一个空目录,发现this:

zip.writestr(zipfile.ZipInfo('empty/'), '')

这样,您应该能够编写必要的代码来使其归档空目录。

【讨论】:

【参考方案3】:

这是从Adding folders to a zip file using python 提升的,但这是我尝试过的唯一有效的功能。作为答案列出的那个在 Python 2.7.3 下不起作用(不复制空目录并且效率低下)。以下是经过尝试和测试的:

#!/usr/bin/python
import os
import zipfile

def zipdir(dirPath=None, zipFilePath=None, includeDirInZip=True):

    if not zipFilePath:
        zipFilePath = dirPath + ".zip"
    if not os.path.isdir(dirPath):
        raise OSError("dirPath argument must point to a directory. "
        "'%s' does not." % dirPath)
    parentDir, dirToZip = os.path.split(dirPath)
    #Little nested function to prepare the proper archive path
    def trimPath(path):
        archivePath = path.replace(parentDir, "", 1)
        if parentDir:
            archivePath = archivePath.replace(os.path.sep, "", 1)
        if not includeDirInZip:
            archivePath = archivePath.replace(dirToZip + os.path.sep, "", 1)
        return os.path.normcase(archivePath)

    outFile = zipfile.ZipFile(zipFilePath, "w",
        compression=zipfile.ZIP_DEFLATED)
    for (archiveDirPath, dirNames, fileNames) in os.walk(dirPath):
        for fileName in fileNames:
            filePath = os.path.join(archiveDirPath, fileName)
            outFile.write(filePath, trimPath(filePath))
        #Make sure we get empty directories as well
        if not fileNames and not dirNames:
            zipInfo = zipfile.ZipInfo(trimPath(archiveDirPath) + "/")
            #some web sites suggest doing
            #zipInfo.external_attr = 16
            #or
            #zipInfo.external_attr = 48
            #Here to allow for inserting an empty directory.  Still TBD/TODO.
            outFile.writestr(zipInfo, "")
    outFile.close()

【讨论】:

16(或 0x10)是目录的 MS-DOS 标志。您应该设置它,而且 UNIX 也应该设置它。如果您使用ZipInfo.from_path,那么它将为您处理。否则某些程序会将您解压缩为空文件而不是目录。【参考方案4】:
def zip_dir(src_dir, dst_zip, *, skip_suffixes=None, dry=False):
    import logging
    from pathlib import Path
    from os import walk
    from tempfile import TemporaryDirectory
    from zipfile import ZipFile, ZipInfo

    _log = logging.getLogger(zip_dir.__name__)
    _log.addHandler(logging.NullHandler())
    _sep = 50 * "-"

    skip_suffixes = skip_suffixes or []
    src_dir, dst_zip = Path(src_dir), Path(dst_zip)
    _log.info("zipping dir: '%s' to: '%s", str(src_dir), str(dst_zip))

    if not src_dir.exists():
        raise FileNotFoundError(str(src_dir))
    if not src_dir.is_dir():
        raise NotADirectoryError(str(src_dir))
    if dst_zip.exists():
        raise FileExistsError(str(dst_zip))

    with TemporaryDirectory() as tmp_dir:
        tmp_zip_path = Path(tmp_dir).joinpath(dst_zip.name)

        with ZipFile(str(tmp_zip_path), mode="w") as zip_out:
            for root, dirs, files in walk(src_dir):
                root = Path(root)

                for folder in dirs:
                    folder = root.joinpath(folder)

                    # add empty folders to the zip
                    if not list(folder.iterdir()):
                        _log.debug(_sep)
                        folder_name = f"str(folder.relative_to(src_dir))/"
                        _log.debug("empty dir: '%s'", folder_name)

                        if dry:
                            continue

                        zip_out.writestr(ZipInfo(folder_name), "")

                for file in files:
                    file = root.joinpath(file)
                    _log.debug(_sep)
                    _log.debug("adding:  '%s'", str(file))

                    should_skip = None
                    for suffix in file.suffixes:
                        if suffix in skip_suffixes:
                            should_skip = suffix
                            break

                    if should_skip:
                        _log.debug("skipped [%s]: %s", should_skip, str(file))
                        continue

                    arcname = str(file.relative_to(src_dir))
                    _log.debug("arcname: '%s'", arcname)

                    if dry:
                        continue

                    zip_out.write(str(file), arcname=arcname)

        if not dry:
            dst_zip.write_bytes(tmp_zip_path.read_bytes())

        tmp_zip_path.unlink()

if __name__ == '__main__':
    import logging
    logging.basicConfig(level=logging.DEBUG, format="%(asctime)s | %(levelname)8s | %(module)25s:%(lineno)-5s | %(message)s")

    zip_dir("/tmp/opera_profile", "opera_profile.zip", skip_suffixes=[".log"], dry=True)

【讨论】:

以上是关于如何使用 Python 创建文件路径的 zip 文件,包括空目录?的主要内容,如果未能解决你的问题,请参考以下文章

python/zip:如果提供了文件的绝对路径,如何消除 zip 存档中的绝对路径?

使用Python计算zip文件中具有不同格式的文件数

Python打开zip文件

如何创建一个py文件

Python - 如何将多个文件夹从路径压缩到单个 zip 文件?

获取 zip 文件中的文件夹名称 - Python