【问题标题】:Python zipfile does not unzip folders for windows zip archivePython zipfile 不会解压缩 Windows zip 存档的文件夹
【发布时间】:2019-02-05 00:47:36
【问题描述】:

我有一个使用此工具System.IO.Compression.ZipFileWindows 机器上创建的 zip 文件(此 zip 存档包含许多文件和文件夹)。我有一个在 Linux 机器(确切地说是树莓派)上运行的 python 代码,它必须解压缩存档并创建所有必要的文件夹和文件。我正在使用Python 3.5.0zipfile 库,这是一个示例代码:

import zipfile

zip = zipfile.ZipFile("MyArchive.zip","r")
zip.extractall()
zip.close()

现在,当我运行这段代码而不是得到一个很好的解压缩目录树时,我得到了根目录中的所有文件,它们的名字很奇怪,比如Folder1\Folder2\MyFile.txt

我的假设是,由于 zip 存档是在 Windows 上创建的,并且 Windows 上的目录分隔符是 \ 而在 Linux 上是 /,python zipfile 库将 \ 视为文件名而不是目录的一部分分隔器。另请注意,当我手动(而不是通过 python 代码)提取此存档时,所有文件夹都按预期创建,因此看来这绝对是zipfile 库的问题。另一个注意事项是,对于使用不同工具(不是System.IO.Compression.ZipFile)创建的 zip 档案,使用相同的 python 代码可以正常工作。

关于发生了什么以及如何解决它的任何见解?

【问题讨论】:

  • Zip 文件将所有内容存储在根目录中。层次结构实际上是通过文件名中的斜线创建的。
  • 显示如何手动提取存档
  • @MadPhysicist 这是所有的 Python 代码。或者你问的是什么代码?
  • @MadPhysicist 非常简单 sudo unzip MyArchive.zip 这工作正常。
  • 在拨打extractall之前,请在任何地方尝试:from os import path; path.altsep = '\\'

标签: python linux python-3.x archive zipfile


【解决方案1】:

发生的情况是,虽然 Windows 将 \ (path.sep) 和 / (path.altsep) 识别为路径分隔符,但 Linux 仅识别 / (path.sep)。

正如@blhsing's answer 所示,ZipFile 的现有实现始终确保path.sep/ 被视为有效的分隔符。这意味着在 Linux 上,\ 被视为文件名的文字部分。要更改它,您可以将 os.altsep 设置为 \,因为它会检查它是否不为空。

如果您沿着修改ZipFile 本身的道路前进,就像其他答案所建议的那样,只需添加一行以盲目地将\ 更改为path.sep,因为无论如何/ 总是已经更改。这样,/\ 和可能的path.altsep 都将转换为path.sep。这就是命令行工具似乎正在做的事情。

【讨论】:

    【解决方案2】:

    这确实是zipfile module 的一个错误,它在ZipFile._extract_member() 中有以下行来盲目地将文件名中的'/' 替换为特定于操作系统的路径分隔符,此时它还应该寻找@987654325 @:

    arcname = member.filename.replace('/', os.path.sep)
    

    您可以通过使用直接从源代码复制但已更正上述行的版本覆盖 ZipFile._extract_member() 来解决此问题:

    from zipfile import ZipFile, ZipInfo
    import shutil
    import os
    def _extract_member(self, member, targetpath, pwd):
        """Extract the ZipInfo object 'member' to a physical
           file on the path targetpath.
        """
        if not isinstance(member, ZipInfo):
            member = self.getinfo(member)
    
        if os.path.sep == '/':
            arcname = member.filename.replace('\\', os.path.sep)
        else:
            arcname = member.filename.replace('/', os.path.sep)
    
        if os.path.altsep:
            arcname = arcname.replace(os.path.altsep, os.path.sep)
        # interpret absolute pathname as relative, remove drive letter or
        # UNC path, redundant separators, "." and ".." components.
        arcname = os.path.splitdrive(arcname)[1]
        invalid_path_parts = ('', os.path.curdir, os.path.pardir)
        arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
                                   if x not in invalid_path_parts)
        if os.path.sep == '\\':
            # filter illegal characters on Windows
            arcname = self._sanitize_windows_name(arcname, os.path.sep)
    
        targetpath = os.path.join(targetpath, arcname)
        targetpath = os.path.normpath(targetpath)
    
        # Create all upper directories if necessary.
        upperdirs = os.path.dirname(targetpath)
        if upperdirs and not os.path.exists(upperdirs):
            os.makedirs(upperdirs)
    
        if member.is_dir():
            if not os.path.isdir(targetpath):
                os.mkdir(targetpath)
            return targetpath
    
        with self.open(member, pwd=pwd) as source, \
                open(targetpath, "wb") as target:
            shutil.copyfileobj(source, target)
    
        return targetpath
    ZipFile._extract_member = _extract_member
    

    【讨论】:

    • 这没有多大意义,因为该方法已经这样做了。
    • 改用条件语句修复。
    • 保留斜线实际上是有道理的。真正的解决方案是将 altsep 传递给函数。