用Python提取带有无效字符的文件名的文件
我使用Python的zipfile模块来解压一个.zip文件(我们可以用这个文件作为例子:http://img.dafont.com/dl/?f=akvaleir)。
f = zipfile.ZipFile('akvaleir.zip', 'r')
for fileinfo in f.infolist():
print fileinfo.filename
f.extract(fileinfo, '.')
解压后的结果是:
Akval�ir_Normal_v2007.ttf
Akval�ir, La police - The Font - Fr - En.pdf
解压后这两个文件都无法访问,因为它们的文件名中有无效的编码字符。问题在于zipfile模块没有选项可以指定输出的文件名。
不过,使用“unzip akvaleir.zip”命令可以很好地处理文件名:
root@host:~# unzip akvaleir.zip
Archive: akvaleir.zip
inflating: AkvalВir_Normal_v2007.ttf
inflating: AkvalВir, La police - The Font - Fr - En.pdf
我尝试在我的Python程序中捕获“unzip -l akvaleir.zip”的输出,这两个文件名是:
Akval\xd0\x92ir_Normal_v2007.ttf
Akval\xd0\x92ir, La police - The Font - Fr - En.pdf
我该如何在不捕获“unzip -l akvaleir.zip”输出的情况下,获取像unzip命令那样正确的文件名呢?
3 个回答
我在用Docker运行我的应用时遇到了类似的问题。把这些代码加到Dockerfile里,问题就解决了:
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
所以,我想如果你不使用Docker,可以试试这个方法,确保地区设置正确生成并设置好。
花了一些时间,但我想我找到了答案。
我猜这个词应该是“Akvaléir”。我找到了一些关于它的描述,还是法语的。当我用你的代码片段时,我得到了一个字符串,像这样:
>>> fileinfo.filename
'Akval\x82ir, La police - The Font - Fr - En.pdf'
>>>
但是在UTF8、Latin-1、CP-1251或CP-1252编码下都不行。然后我发现CP863可能是一个加拿大的编码,所以也许这个是来自法语加拿大的。
>>> print unicode(fileinfo.filename, "cp863").encode("utf8")
Akvaléir, La police - The Font - Fr - En.pdf
>>>
不过,我接着阅读了ZIP文件格式规范,里面说:
ZIP格式历史上只支持原始的IBM PC字符编码集,通常称为IBM代码页437。
...
如果通用位11被设置,文件名和注释必须支持Unicode标准,版本4.1.0或更高版本,使用UTF-8存储规范定义的字符编码形式。
测试这个结果给了我和加拿大代码页一样的答案。
>>> print unicode(fileinfo.filename, "cp437").encode("utf8")
Akvaléir, La police - The Font - Fr - En.pdf
>>>
我没有一个Unicode编码的zip文件,也不打算去创建一个来验证,所以我就假设所有的zip文件都是cp437编码的。
import shutil
import zipfile
f = zipfile.ZipFile('akvaleir.zip', 'r')
for fileinfo in f.infolist():
filename = unicode(fileinfo.filename, "cp437")
outputfile = open(filename, "wb")
shutil.copyfileobj(f.open(fileinfo.filename), outputfile)
在我的Mac上,这样做得到:
109936 Nov 27 01:46 Akvale??ir_Normal_v2007.ttf
25244 Nov 27 01:46 Akvale??ir, La police - The Font - Fr - En.pdf
这可以通过自动补全得到:
ls Akvale\314\201ir
在我的文件浏览器中显示出一个漂亮的'é'。
与其使用 extract
方法,不如用 open
方法,然后把得到的伪文件保存到磁盘上,命名可以随你喜欢,比如可以用 shutil.copyfileobj
来实现。