Python zipfile 不解压缩 windows zip 存档的文件夹
Python zipfile does not unzip folders for windows zip archive
我有一个 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
库的问题。另一个注意事项是,对于使用不同工具(不是 System.IO.Compression.ZipFile
)创建的 zip 存档,使用相同的 python 代码可以正常工作。
对正在发生的事情以及如何解决它有任何见解吗?
这确实是 zipfile
module 的一个错误,它在 ZipFile._extract_member()
中有以下行来盲目地将文件名中的 '/'
替换为 OS-具体路径分隔符,当它还应该寻找 '\'
:
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
发生的事情是,虽然 Windows 将 \
(path.sep
) 和 /
(path.altsep
) 都识别为路径分隔符,但 Linux 只识别 /
(path.sep
).
如 所示,ZipFile
的现有实现始终确保 path.sep
和 /
被视为有效的分隔符。这意味着在 Linux 上,\
被视为文件名的文字部分。要更改它,您可以将 os.altsep
设置为 \
,因为如果它不是 None 空的,它会被检查。
如果你沿着修改 ZipFile
本身的道路前进,就像其他答案所建议的那样,只需添加一行以盲目地将 \
更改为 path.sep
,因为 /
总是已经改变了。这样一来,/
、\
和可能的path.altsep
都将被转换为path.sep
。这就是命令行工具似乎正在做的事情。
我有一个 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
库的问题。另一个注意事项是,对于使用不同工具(不是 System.IO.Compression.ZipFile
)创建的 zip 存档,使用相同的 python 代码可以正常工作。
对正在发生的事情以及如何解决它有任何见解吗?
这确实是 zipfile
module 的一个错误,它在 ZipFile._extract_member()
中有以下行来盲目地将文件名中的 '/'
替换为 OS-具体路径分隔符,当它还应该寻找 '\'
:
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
发生的事情是,虽然 Windows 将 \
(path.sep
) 和 /
(path.altsep
) 都识别为路径分隔符,但 Linux 只识别 /
(path.sep
).
如 ZipFile
的现有实现始终确保 path.sep
和 /
被视为有效的分隔符。这意味着在 Linux 上,\
被视为文件名的文字部分。要更改它,您可以将 os.altsep
设置为 \
,因为如果它不是 None 空的,它会被检查。
如果你沿着修改 ZipFile
本身的道路前进,就像其他答案所建议的那样,只需添加一行以盲目地将 \
更改为 path.sep
,因为 /
总是已经改变了。这样一来,/
、\
和可能的path.altsep
都将被转换为path.sep
。这就是命令行工具似乎正在做的事情。