Python Zip-файл не распаковывает папки для Windows Zip-архив - PullRequest
0 голосов
/ 30 августа 2018

У меня есть zip-файл, созданный на машине Windows с использованием этого инструмента System.IO.Compression.ZipFile (этот zip-архив содержит много файлов и папок). У меня есть код Python, который работает на машине Linux (точнее, raspberry pi), который должен распаковать архив и создать все необходимые папки и файлы. Я использую библиотеку Python 3.5.0 и zipfile, это пример кода:

import zipfile

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

Теперь, когда я запускаю этот код вместо того, чтобы получить красивое разархивированное дерево каталогов, я получаю все файлы в корневом каталоге со странными именами, такими как Folder1\Folder2\MyFile.txt.

Я предполагаю, что поскольку zip-архив был создан в Windows, а разделитель каталогов в Windows - \, тогда как в Linux это /, библиотека python zipfile обрабатывает \ как часть имени файла, а не каталога. разделитель. Также обратите внимание, что когда я извлекаю этот архив вручную (не через код Python), все папки создаются, как и ожидалось, поэтому, похоже, это определенно проблема библиотеки zipfile. Еще одно замечание: для zip-архивов, созданных с помощью другого инструмента (не System.IO.Compression.ZipFile), он работает нормально, используя тот же код Python.

Любое понимание того, что происходит и как это исправить?

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Происходит следующее: в то время как Windows распознает как \ (path.sep), так и / (path.altsep) как разделители пути, Linux распознает только / (path.sep).

Как показывает ответ @ blhsing , существующая реализация ZipFile всегда гарантирует, что path.sep и / считаются действительными символами-разделителями. Это означает, что в Linux \ рассматривается как буквальная часть имени файла. Чтобы изменить это, вы можете установить os.altsep на \, так как проверяется, не пусто ли оно.

Если вы идете по пути изменения самого ZipFile, как предлагает другой ответ, просто добавьте строку, чтобы слепо изменить \ на path.sep, так как / всегда всегда уже изменяется. Таким образом, /, \ и, возможно, path.altsep будут преобразованы в path.sep. Вот что, похоже, делает инструмент командной строки.

0 голосов
/ 30 августа 2018

Это действительно ошибка модуля zipfile , где в ZipFile._extract_member() имеется следующая строка для слепой замены '/' в именах файлов на разделитель пути, специфичный для ОС, когда следует также поискать '\\':

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
...