简体   繁体   English

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

[英]How do I create a zip file of a file path using Python, including empty directories?

I've been trying to use the zipfile and shutil.make_archive modules to recursively create a zip file of a directory.我一直在尝试使用zipfileshutil.make_archive模块递归创建目录的 zip 文件。 Both modules work great--except empty directories do not get added to the archive.两个模块都工作得很好——除了空目录不会被添加到档案中。 Empty directories containing other empty directories are also silently skipped.包含其他空目录的空目录也会被静默跳过。

I can use 7Zip to create an archive of the same path and empty directories are preserved.我可以使用 7Zip 创建相同路径的存档并保留空目录。 Therefore I know this is possible within the file format itself.因此我知道这在文件格式本身中是可能的。 I just don't know how to do it within Python.我只是不知道如何在 Python 中做到这一点。 Any ideas?有任何想法吗? Thanks!谢谢!

There is a example using zipfile:有一个使用 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')

This is lifted from Adding folders to a zip file using python but is the only function I have tried that works.这是从使用 python 将文件夹添加到 zip 文件中提升的,但这是我尝试过的唯一有效的功能。 The one listed as the answer does not work under Python 2.7.3 (doesn't copy empty directories and is inefficient).作为答案列出的那个在 Python 2.7.3 下不起作用(不复制空目录并且效率低下)。 The following is tried and tested:以下经过尝试和测试:

#!/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()

You'll need to register a new archive format to do that, since the default ZIP archiver does not support that.您需要注册一种新的存档格式才能做到这一点,因为默认的 ZIP 存档器不支持这种格式 Take a look at the meat of the existing ZIP archiver .查看现有 ZIP 归档程序的主要内容 Make your own archiver that creates directories using that currently-unused dirpath variable.使用当前未使用的dirpath变量创建您自己的归档程序。 I looked for how to create an empty directory and found this :我寻找如何创建一个空目录并找到了这个

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

With that, you should be able to write the necessary code to make it archive empty directories.有了这个,您应该能够编写必要的代码以使其归档空目录。

(I have still no reputation and can't comment directly) (我仍然没有声誉,不能直接发表评论)

Suggesting edit to James's answer, 建议编辑詹姆斯的答案,

Either change: 'def zipdir(dirPath=None, zipFilePath=None,....' to: 'def zipdir(dirPath,zipFilePath=None,...' Or remove the 'if not os.path.isdir(dirPath):' condition and it's body 将'def zipdir(dirPath = None,zipFilePath = None,....')更改为:'def zipdir(dirPath,zipFilePath = None,...')或删除'if not os.path.isdir(dirPath) :'条件和身体

Otherwise you'll have issues because of this inconsistency. 否则,由于这种不一致,您将遇到问题。

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM