执行os.walk时出现UnicodeDecodeError

Sco*_*ott 19 python unicode encoding utf-8 utf-16

我收到错误:

'ascii' codec can't decode byte 0x8b in position 14: ordinal not in range(128)
Run Code Online (Sandbox Code Playgroud)

当试图做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'
Run Code Online (Sandbox Code Playgroud)

这是我得到的错误的回溯:

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)
Run Code Online (Sandbox Code Playgroud)

问题的根源发生在listdir返回的文件列表中(在os.walk的第276行):

names = listdir(top)
Run Code Online (Sandbox Code Playgroud)

chars> 128的名称将作为非unicode字符串返回.

小智 8

我只是花了一些时间来整理这个错误,而且这里的wordier答案没有解决潜在的问题:

问题是,如果你传递一个Unicode字符串 os.walk(),然后os.walk开始得到统一从os.listdir回(),并试图保持它作为ASCII(因此是"ASCII"解码错误).当它只击中一个unicode只有str()无法翻译的特殊字符时,它会抛出异常.

解决方案是迫使你传递给os.walk是一个普通字符串起始路径-即os.walk(STR(somepath)).这意味着os.listdir返回类似字节的常规字符串,一切都按照应有的方式工作.

你可以重现这个问题(并显示它的解决方案是有效的),如:

  1. 进入某个目录中的bash并运行touch $(echo -e "\x8b\x8bThis is a bad filename")将生成一些测试文件.

  2. 现在在同一目录中运行以下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
    
    Run Code Online (Sandbox Code Playgroud)

你会得到一个UnicodeDecodeError.

  1. 现在尝试运行:

    l = []
    for root,dir,filenames in os.walk('.'):
        l.extend([ os.path.join(root, f) for f in filenames ])
    print l
    
    Run Code Online (Sandbox Code Playgroud)

没有错误,你打印出来!

因此,Python 2.x中的安全方法是确保只将原始文本传递给os.walk().你绝对不应该将unicode或可能是unicode的东西传递给它,因为当内部ascii转换失败时os.walk将会阻塞.


jfs*_*jfs 6

我可以重现os.listdir()行为:os.listdir(unicode_name)在Python 2.7上返回undecodable条目作为字节:

>>> import os
>>> os.listdir(u'.')
[u'abc', '<--\x8b-->']
Run Code Online (Sandbox Code Playgroud)

注意:第二个名称是字节listdir()串,尽管参数是Unicode字符串.

然而,一个很大的问题仍然是如何在不诉诸这种黑客的情况下解决这个问题?

Python 3通过surrogateescape错误处理程序(os.fsencode/os.fsdecode)解决文件名中的不可解码字节(使用文件系统的字符编码)字节.请参阅PEP-383:系统字符接口中的不可解码字节:

>>> os.listdir(u'.')
['abc', '<--\udc8b-->']
Run Code Online (Sandbox Code Playgroud)

注意:两个字符串都是Unicode(Python 3).而surrogateescape用于第二个名称错误处理程序.要获取原始字节:

>>> os.fsencode('<--\udc8b-->')
b'<--\x8b-->'
Run Code Online (Sandbox Code Playgroud)

在Python 2中,在Windows(Unicode API),OS X(强制使用utf-8)上使用Unicode字符串作为文件名,并在Linux和其他系统上使用字节串.


Sco*_*ott 6

这个问题源于两个基本问题.第一个事实是Python 2.x默认编码是'ascii',而默认的Linux编码是'utf8'.您可以通过以下方式验证这些编码:

sys.getdefaultencoding() #python
sys.getfilesystemencoding() #OS
Run Code Online (Sandbox Code Playgroud)

当os模块函数返回目录内容时,即os.walk和os.listdir返回包含仅ascii文件名和非ascii文件名的文件列表,ascii编码文件名将自动转换为unicode.其他人不是.因此,结果是包含unicode和str对象混合的列表.str对象可能导致问题.由于它们不是ascii,因此python无法知道要使用的编码,因此它们无法自动解码为unicode.

因此,当执行诸如os.path(dir,file)之类的常见操作时,其中dir是unicode而file是编码的str,如果文件不是ascii编码(默认值),则此调用将失败.解决方案是在检索每个文件名后立即检查它们,并使用适当的编码将str(编码的)对象解码为unicode.

这是第一个问题及其解决方案.第二个有点棘手.由于这些文件最初来自Windows系统,因此它们的文件名可能使用名为windows-1252的编码.一种简单的检查方法是致电:

filename.decode('windows-1252')
Run Code Online (Sandbox Code Playgroud)

如果有效的unicode版本导致您可能具有正确的编码.您还可以通过在unicode版本上调用print进一步验证,并查看正确的文件名呈现.

最后一个皱纹.在具有Windows源文件的Linux系统中,可能甚至可能混合使用windows-1252utf8编码.处理这种混合物有两种方法.第一个也是最好的是运行:

$ convmv -f windows-1252 -t utf8 -r DIRECTORY --notest
Run Code Online (Sandbox Code Playgroud)

其中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
Run Code Online (Sandbox Code Playgroud)

该函数首先尝试utf8解码.如果失败,那么它将回退到windows-1252版本.在os调用返回文件列表后使用此函数:

root, dirs, files = os.walk(path):
    files = [decodeName(f) for f in files]
    # do something with the unicode filenames now
Run Code Online (Sandbox Code Playgroud)

我个人发现unicode和编码的整个主题非常令人困惑,直到我读到这个精彩而简单的教程:

http://farmdev.com/talks/unicode/

我强烈建议任何努力解决unicode问题的人.

  • -1。这仍然是错误的。默认编码与它无关。Python不使用它来解码文件名。无法解码的文件名(顾名思义)无法使用* any *字符编码进行解码。看我的答案。 (2认同)