【问题标题】:Unzipping directory structure with python用python解压目录结构
【发布时间】:2010-10-13 00:03:34
【问题描述】:

我有一个包含以下目录结构的 zip 文件:

dir1\dir2\dir3a
dir1\dir2\dir3b

我正在尝试解压缩并维护目录结构,但出现错误:

IOError: [Errno 2] No such file or directory: 'C:\\\projects\\\testFolder\\\subdir\\\unzip.exe'

其中 testFolder 是上面的 dir1,subdir 是 dir2。

有没有快速解压文件和维护目录结构的方法?

【问题讨论】:

标签: python unzip


【解决方案1】:

如果您使用的是 Python 2.6,则 extract 和 extractall 方法非常有用。我现在必须使用 Python 2.5,所以如果它们不存在,我只需要创建目录。您可以使用namelist() 方法获取目录列表。目录将始终以正斜杠结尾(即使在 Windows 上),例如,

import os, zipfile

z = zipfile.ZipFile('myfile.zip')
for f in z.namelist():
    if f.endswith('/'):
        os.makedirs(f)

您可能不想完全那样做(即,您可能希望在遍历名称列表时提取 zip 文件的内容),但您会得到想法。

【讨论】:

  • 可能想将os.makedirs(f) 包装起来尝试一下:except (OSError,WindowsError): 阻止以防文件夹已存在。
  • 但是如何解压"f"目录下的文件(os.makedirs(f)) ??
【解决方案2】:

不要相信 extract() 或 extractall()。

这些方法会盲目地将文件提取到文件名中给出的路径。但是 ZIP 文件名可以是任何东西,包括像“x/../../../etc/passwd”这样的危险字符串。提取此类文件,您可能会破坏整个服务器。

也许这应该被认为是 Python 的 zipfile 模块中的一个可报告的安全漏洞,但过去任何数量的 zip-dearchivers 都表现出完全相同的行为。要安全地解压缩具有文件夹结构的 ZIP 文件,您需要深入检查每个文件路径。

【讨论】:

  • 为什么不认为它是一个错误呢?特别是因为它是“新”代码,它是为 Python 2.6 添加的。装这种洞简直是愚蠢。
  • 这里没有理由担心安全问题,您必须是 Hulk Hogan 才能在 root 权限下运行您的应用程序。
  • etc/passwd 只是一个例子;在不以 root 身份运行的情况下,将文件拖放到任意文件系统位置有多种方式会带来安全风险。经典地将 something.php.htaccess 等文件放入可执行位置,或覆盖运行时数据。
  • 看起来它自 2.7.4 以来发生了变化:docs.python.org/2/library/zipfile#zipfile.ZipFile.extractall "zipfile 模块试图阻止这种情况。"
【解决方案3】:

我试过了,可以重现它。正如其他答案所建议的, extractall 方法不能解决问题。对我来说,这似乎是 zipfile 模块中的一个错误(可能仅限 Windows?),除非我误解了 zipfile 的结构。

testa\
testa\testb\
testa\testb\test.log
> test.zip

>>> from zipfile import ZipFile
>>> zipTest = ZipFile("C:\\...\\test.zip")
>>> zipTest.extractall("C:\\...\\")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "...\zipfile.py", line 940, in extractall
  File "...\zipfile.py", line 928, in extract
  File "...\zipfile.py", line 965, in _extract_member
IOError: [Errno 2] No such file or directory: 'C:\\...\\testa\\testb\\test.log'

如果我执行printdir(),我会得到这个(第一列):

>>> zipTest.printdir()
File Name
testa/testb/
testa/testb/test.log

如果我尝试只提取第一个条目,如下所示:

>>> zipTest.extract("testa/testb/")
'C:\\...\\testa\\testb'

在磁盘上,这会创建一个文件夹testa,其中包含一个文件 testb。这显然是后续尝试提取test.log 失败的原因; testa\testb 是文件,不是文件夹。

编辑#1:如果你只提取文件,那么它可以工作:

>>> zipTest.extract("testa/testb/test.log")
'C:\\...\\testa\\testb\\test.log'

编辑#2:杰夫的代码是要走的路;遍历namelist;如果是目录,则创建目录。否则,解压文件。

【讨论】:

    【解决方案4】:

    我知道现在说这个可能有点晚,但 Jeff 是对的。 很简单:

    import os
    from zipfile import ZipFile as zip
    
    def extractAll(zipName):
        z = zip(zipName)
        for f in z.namelist():
            if f.endswith('/'):
                os.makedirs(f)
            else:
                z.extract(f)
    
    if __name__ == '__main__':
        zipList = ['one.zip', 'two.zip', 'three.zip']
        for zip in zipList:
            extractAll(zipName)
    

    【讨论】:

    • 看来zipfile.ZipFile(zip_name).extractall() 就是这么做的。
    【解决方案5】:

    如果您使用的是 Python 2.6,有一个非常简单的方法:extractall 方法。

    但是,由于zipfile 模块完全在 Python 中实现,没有任何 C 扩展,您可能可以将其从 2.6 安装中复制出来并与旧版本的 Python 一起使用;您可能会发现这比自己重新实现功能更容易。但是,函数本身很短:

    def extractall(self, path=None, members=None, pwd=None):
        """Extract all members from the archive to the current working
           directory. `path' specifies a different directory to extract to.
           `members' is optional and must be a subset of the list returned
           by namelist().
        """
        if members is None:
            members = self.namelist()
    
        for zipinfo in members:
            self.extract(zipinfo, path, pwd)
    

    【讨论】:

    • 我试过这个,不幸的是,我遇到了下面指出的问题。
    【解决方案6】:

    听起来您正在尝试运行 unzip 以提取 zip。

    最好使用pythonzipfile模块,因此在python中进行提取。

    import zipfile
    
    def extract(zipfilepath, extractiondir):
        zip = zipfile.ZipFile(zipfilepath)
        zip.extractall(path=extractiondir)
    

    【讨论】:

    • 注意pwd是文件的密码;要提取到的路径的参数是“路径”。
    • 对不起,我的错 - 你可以说我编写了代码而没有运行它。 :-)
    • 也应该是zip = zipfile.ZipFile(zipfilepath)
    【解决方案7】:

    过滤名单以排除文件夹

    你所要做的就是过滤掉以/结尾的namelist()条目,问题就解决了:

      z.extractall(dest, filter(lambda f: not f.endswith('/'), z.namelist()))
    

    开心!

    【讨论】:

      【解决方案8】:

      如果像我一样,您必须使用较旧的 Python 版本(在我的情况下为 2.4)提取完整的 zip 存档,这是我想出的(基于 Jeff 的回答):

      import zipfile
      import os
      
      def unzip(source_file_path, destination_dir):
          destination_dir += '/'
          z = zipfile.ZipFile(source_file_path, 'r')
          for file in z.namelist():
              outfile_path = destination_dir + file
              if file.endswith('/'):
                  os.makedirs(outfile_path)
              else:
                  outfile = open(outfile_path, 'wb')
                  outfile.write(z.read(file))
                  outfile.close()
          z.close()
      

      【讨论】:

        【解决方案9】:

        请注意,zip 文件可以包含目录条目和文件条目。使用zip 命令创建存档时,传递-D 选项以禁用将目录条目显式添加到存档。当 Python 2.6 的 ZipFile.extractall 方法在目录条目中运行时,它似乎会在其位置创建一个 file。由于存档条目不一定按顺序排列,这会导致ZipFile.extractall 经常失败,因为它试图在文件的子目录中创建文件。如果您有一个想要与 Python 模块一起使用的存档,只需将其解压缩并使用 -D 选项重新压缩即可。这是我已经使用了一段时间的小 sn-p 来做到这一点:

        P=`pwd` && 
        Z=`mktemp -d -t zip` && 
        pushd $Z && 
        unzip $P/<busted>.zip && 
        zip -r -D $P/<new>.zip . && 
        popd && 
        rm -rf $Z
        

        &lt;busted&gt;.zip&lt;new&gt;.zip 替换为相对于当前目录的真实文件名。然后只需复制整个内容并将其粘贴到命令 shell 中,它就会创建一个新的存档文件,该存档文件已准备好与 Python 2.6 一起使用。 有一个zip 命令可以在不解压缩的情况下删除这些目录条目,但 IIRC 它在不同的 shell 环境或 zip 配置中表现得很奇怪。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-04-12
          • 2016-08-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多