我收到错误:
'ascii' codec can't decode byte 0x8b in position 14: ordinal not in range(128)
试图做os.walk时。发生此错误是因为目录中的某些文件中包含0x8b(非utf8)字符。这些文件来自Windows系统(因此是utf-16文件名),但我已将文件复制到Linux系统,并使用python 2.7(在Linux中运行)遍历目录。
我已经尝试将一个unicode启动路径传递给os.walk,以及所有文件&它生成的dirs是unicode名称,直到它出现非utf8名称,然后由于某种原因,它不会将这些名称转换为unicode,然后代码会在utf-16名称上窒息。无论如何都要解决这个问题,而不是手动查找和更改所有令人反感的名字?
如果python2.7中没有解决方案,是否可以在python3中编写脚本来遍历文件树并通过将它们转换为utf-8来修复坏文件名(通过删除非utf8字符)?注:除了0x8b之外,名称中还有许多非utf8字符,因此它需要以一般方式工作。
更新:0x8b仍然只是一个btye char(只是无效的ascii)的事实使它更令人费解。我已经验证将这样的字符串转换为unicode存在问题,但是可以直接创建unicode版本。即:
>>> test = 'a string \x8b with non-ascii'
>>> test
'a string \x8b with non-ascii'
>>> unicode(test)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0x8b in position 9: ordinal not in range(128)
>>>
>>> test2 = u'a string \x8b with non-ascii'
>>> test2
u'a string \x8b with non-ascii'
这是我得到的错误的追溯:
80. for root, dirs, files in os.walk(unicode(startpath)):
File "/usr/lib/python2.7/os.py" in walk
294. for x in walk(new_path, topdown, onerror, followlinks):
File "/usr/lib/python2.7/os.py" in walk
294. for x in walk(new_path, topdown, onerror, followlinks):
File "/usr/lib/python2.7/os.py" in walk
284. if isdir(join(top, name)):
File "/usr/lib/python2.7/posixpath.py" in join
71. path += '/' + b
Exception Type: UnicodeDecodeError at /admin/casebuilder/company/883/
Exception Value: 'ascii' codec can't decode byte 0x8b in position 14: ordinal not in range(128)
问题的根源发生在listdir返回的文件列表中(在os.walk的第276行):
names = listdir(top)
带字符的名字&gt; 128作为非unicode字符串返回。
答案 0 :(得分:7)
对,我只是花了一些时间来整理这个错误,而这里的wordier答案没有解决潜在的问题:
问题是,如果你将一个unicode字符串传递给 os.walk(),那么os.walk开始从os.listdir()返回unicode并尝试将其保持为ASCII(因此'ascii'解码错误)。当它只击中一个unicode只有str()无法翻译的特殊字符时,它会抛出异常。
解决方案是强制您传递给os.walk的起始路径是常规字符串 - 即os.walk(str(somepath))。这意味着os.listdir返回类似于字节的常规字符串,一切都按照应有的方式工作。
你可以重现这个问题(并显示它的解决方案有效),如:
进入某个目录中的bash并运行touch $(echo -e "\x8b\x8bThis is a bad filename")
,这将生成一些测试文件。
现在在同一目录中运行以下Python代码(iPython Qt非常方便):
l = []
for root,dir,filenames in os.walk(unicode('.')):
l.extend([ os.path.join(root, f) for f in filenames ])
print l
你会得到一个UnicodeDecodeError。
现在尝试运行:
l = []
for root,dir,filenames in os.walk('.'):
l.extend([ os.path.join(root, f) for f in filenames ])
print l
没有错误,你打印出来了!
因此,Python 2.x中的安全方法是确保只将原始文本传递给os.walk()。你绝对不应该传递unicode或可能是unicode的东西,因为当内部ascii转换失败时os.walk将会阻塞。
答案 1 :(得分:6)
我可以重现os.listdir()
行为:os.listdir(unicode_name)
在Python 2.7上以字节形式返回不可解码的条目:
>>> import os
>>> os.listdir(u'.')
[u'abc', '<--\x8b-->']
注意:第二个名称是一个bytestring,尽管listdir()
的参数是一个Unicode字符串。
然而,一个很大的问题仍然存在 - 如何在不诉诸此黑客的情况下解决这个问题?
Python 3通过surrogateescape
错误处理程序(os.fsencode/os.fsdecode
)解决文件名中不可解码的字节(使用文件系统的字符编码)字节。见PEP-383: Non-decodable Bytes in System Character Interfaces:
>>> os.listdir(u'.')
['abc', '<--\udc8b-->']
注意:两个字符串都是Unicode(Python 3)。 surrogateescape
错误处理程序用于第二个名称。要恢复原始字节:
>>> os.fsencode('<--\udc8b-->')
b'<--\x8b-->'
在Python 2中,在Windows(Unicode API),OS X(强制使用utf-8)上使用Unicode字符串作为文件名,并在Linux和其他系统上使用字节串。
答案 2 :(得分:5)
这个问题源于两个基本问题。第一个事实是Python 2.x默认编码是'ascii',而默认的Linux编码是'utf8'。您可以通过以下方式验证这些编码:
sys.getdefaultencoding() #python
sys.getfilesystemencoding() #OS
当os模块函数返回目录内容时,即os.walk&amp; os.listdir返回包含ascii only filesnames和non-ascii文件名的文件列表,ascii-encoding文件名自动转换为unicode。其他人不是。因此,结果是包含unicode和str对象混合的列表。 str对象可能导致问题。由于它们不是ascii,因此python无法知道要使用的编码,因此它们无法自动解码为unicode。
因此,当执行os.path(dir,file)等常用操作时,其中 dir 是unicode而文件是编码的str,如果 dil 是编码的str,则此调用将失败该文件不是ascii编码(默认)。解决方案是检索每个文件名,并使用适当的编码将str(编码的)对象解码为unicode。
这是第一个问题及其解决方案。第二个有点棘手。由于文件最初来自Windows系统,因此它们的文件名可能使用名为 windows-1252 的编码。一种简单的检查方法是致电:
filename.decode('windows-1252')
如果有效的unicode版本可能会产生正确的编码。您还可以通过在unicode版本上调用 print 进一步验证,并查看正确的文件名。
最后一个皱纹。在具有Windows源文件的Linux系统中,可能甚至可能混合使用 windows-1252 和 utf8 编码。处理这种混合物有两种方法。第一个也是最好的是运行:
$ convmv -f windows-1252 -t utf8 -r DIRECTORY --notest
其中DIRECTORY是包含需要转换的文件的。此命令会将任何windows-1252编码的文件名转换为utf8。它进行了智能转换,因为如果文件名已经是utf8(或ascii),它将什么都不做。
替代方案(如果出于某种原因无法进行此转换)是在python中动态执行类似的操作。即:
def decodeName(name):
if type(name) == str: # leave unicode ones alone
try:
name = name.decode('utf8')
except:
name = name.decode('windows-1252')
return name
该函数首先尝试utf8解码。如果失败,那么它将回退到windows-1252版本。在os调用返回文件列表后使用此函数:
root, dirs, files = os.walk(path):
files = [decodeName(f) for f in files]
# do something with the unicode filenames now
我个人发现unicode和编码的整个主题非常令人困惑,直到我读到这个精彩而简单的教程:
http://farmdev.com/talks/unicode/
我强烈建议那些努力解决unicode问题的人。
答案 3 :(得分:1)
\ x8不是有效的utf-8编码字符。 os.path期望文件名在utf-8中。如果要访问无效的文件名,则必须将os.path.walk传递给非unicode startpath;这样os模块就不会进行utf8解码。您必须自己完成并决定如何处理包含错误字符的文件名。
即:
for root, dirs, files in os.walk(startpath.encode('utf8')):
答案 4 :(得分:1)
在检查错误源之后,在C代码例程listdir中发生了一些事情,当它们不是标准的ascii时会返回非unicode文件名。因此唯一的解决方法是在os.walk中对目录列表进行强制解码,这需要替换os.walk。这个替换功能有效:
def asciisafewalk(top, topdown=True, onerror=None, followlinks=False):
"""
duplicate of os.walk, except we do a forced decode after listdir
"""
islink, join, isdir = os.path.islink, os.path.join, os.path.isdir
try:
# Note that listdir and error are globals in this module due
# to earlier import-*.
names = os.listdir(top)
# force non-ascii text out
names = [name.decode('utf8','ignore') for name in names]
except os.error, err:
if onerror is not None:
onerror(err)
return
dirs, nondirs = [], []
for name in names:
if isdir(join(top, name)):
dirs.append(name)
else:
nondirs.append(name)
if topdown:
yield top, dirs, nondirs
for name in dirs:
new_path = join(top, name)
if followlinks or not islink(new_path):
for x in asciisafewalk(new_path, topdown, onerror, followlinks):
yield x
if not topdown:
yield top, dirs, nondirs
添加以下行: names = [name.decode('utf8','ignore')名称中的名称] 所有的名字都是正确的ascii&amp; unicode,一切正常。
然而,一个很大的问题仍然存在 - 如何在不诉诸此黑客的情况下解决这个问题?
答案 5 :(得分:0)
在使用中文(unicode)名称的某些目录上使用os.walk
时遇到此问题。我自己实现了walk函数,如下所示,它可以很好地使用unicode目录/文件名。
import os
ft = list(tuple())
def walk(dir, cur):
fl = os.listdir(dir)
for f in fl:
full_path = os.path.join(dir,f)
if os.path.isdir(full_path):
walk(full_path, cur)
else:
path, filename = full_path.rsplit('/',1)
ft.append((path, filename, os.path.getsize(full_path)))