【问题标题】:UnicodeDecodeError when performing os.walk执行 os.walk 时出现 UnicodeDecodeError
【发布时间】:2014-03-13 09:29:50
【问题描述】:

我收到错误:

'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,它生成的所有文件和目录都是 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)

字符数 > 128 的名称将作为非 unicode 字符串返回。

【问题讨论】:

  • 我猜你可以捕获异常并单独处理它们?
  • 你能显示完整的回溯吗?
  • “非UTF8”是什么意思?字节 0x8b 作为 ASCII 肯定是无效的,但是我们需要查看以下字节来知道它是否作为 UTF-8 有效。仅仅因为你看到一个字节 0x8b 并不意味着它试图将 U+008B 表示为一个字符
  • 试试:os.walk(unicode(path)).

标签: python unicode encoding utf-8 utf-16


【解决方案1】:

\x8 不是有效的 utf-8 编码字符。 os.path 期望文件名使用 utf-8 格式。如果要访问无效文件名,则必须将 os.path.walk 传递给非 unicode 起始路径;这样 os 模块将不会进行 utf8 解码。您必须自己做,并决定如何处理包含错误字符的文件名。

即:

for root, dirs, files in os.walk(startpath.encode('utf8')):

【讨论】:

  • 这似乎不起作用,我也不知道它会如何 - 请参阅下面的最终解决方案。
  • @ondra 您的解决方案有效,但是... >您必须自己做,并决定如何处理包含不正确字符的文件名。我现在正在寻找那个解决方案一段时间,如何做到这一点,但例外情况会离开步行循环。我正在将 walk os 数据放入 sqlite 数据库。
【解决方案2】:

在检查错误源之后,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') for name in names] 所有名称都是正确的 ascii 和 unicode,并且一切正常。

然而,一个大问题仍然存在 - 如何在不使用这种 hack 的情况下解决这个问题?

【讨论】:

  • 此方法删除包含不可解码字节的目录和文件,并引入文件系统上不存在的名称或重复现有名称(不好)。
  • J.F.塞巴斯蒂安:你完全正确。我终于找到了不涉及任何黑客的正确解决方案。我已将其标记为正确的。
【解决方案3】:

我可以重现 os.listdir() 行为:os.listdir(unicode_name) 在 Python 2.7 上以字节形式返回不可解码的条目:

>>> import os
>>> os.listdir(u'.')
[u'abc', '<--\x8b-->']

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

然而,一个大问题仍然存在 - 如何在不使用这种 hack 的情况下解决这个问题?

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 和其他系统上使用字节字符串。

【讨论】:

  • 由于我使用的是python 2.x,当我无法控制这些编码文件名时,有没有办法解决这个问题?顺便说一句,这些文件起源于 Windows 系统,但现在驻留在 Linux 系统上。
  • @Scott:是的。如果文件名可以是任意字节序列,那么只需使用字节。如果将字节传递给os.walk();它返回字节。如果您认为文件名没有损坏,您可以稍后将它们解码为 Unicode(如果您有很多名称,应该可以猜出字符编码)。
【解决方案4】:

这个问题源于两个基本问题。首先是 Python 2.x 默认编码是“ascii”,而默认 Linux 编码是“utf8”。您可以通过以下方式验证这些编码:

sys.getdefaultencoding() #python
sys.getfilesystemencoding() #OS

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

因此,在执行 os.path(dir, file) 等常见操作时,其中 dir 是 unicode,file 是编码的 str,如果出现以下情况,此调用将失败该文件不是 ascii 编码的(默认)。解决方案是在检索到每个文件名后立即检查它们,并使用适当的编码将 str(编码的)对象解码为 un​​icode。

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

filename.decode('windows-1252')

如果结果是有效的 unicode 版本,则您可能具有正确的编码。您也可以通过在 unicode 版本上调用 print 来进一步验证,并查看呈现的正确文件名。

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

$ 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 和 encoding 的整个主题非常混乱,直到我阅读了这个精彩而简单的教程:

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

我强烈推荐给所有遇到 unicode 问题的人。

【讨论】:

  • -1。它仍然是错误的。默认编码与它无关。 Python 不使用它来解码文件名。不可解码的文件名(顾名思义)无法使用 any 字符编码进行解码。看我的回答。
  • 在 Windows 上对我来说效果很好。那谢谢啦!最好的解决方案,即使有特殊情况可能行不通。
  • 谢谢!唯一对我也有效的解决方案。在我应用此解决方案之前,使用 os.walk 遇到了令人讨厌的编码问题。当我想使用 root (将它与文件中的每个文件连接起来)时,我必须以这种方式再次调用该函数:os.path.join(self.decodeName(root), file)。
  • @SimonSteinberger 这个解决方案可能会导致 mojibake(你的数据被默默地破坏了)
【解决方案5】:

是的,我只是花了一些时间来整理这个错误,这里更冗长的答案并没有解决根本问题:

问题是,如果你将一个 unicode 字符串 传入 os.walk(),那么 os.walk 开始从 os.listdir() 获取 unicode 并尝试将其保留为 ASCII(因此'ascii' 解码错误)。当它遇到 str() 无法翻译的仅 unicode 特殊字符时,它会抛出异常。

解决方案是强制您传递给 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
    

你会得到一个 UnicodeDecodeError。

  1. 现在尝试运行:

    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 的东西传递给它,因为 os.walk 会在内部 ascii 转换失败时阻塞。

【讨论】:

    【解决方案6】:

    我在一些带有中文(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)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-17
      • 2021-08-19
      • 2019-01-10
      • 1970-01-01
      • 2011-04-14
      • 2018-02-04
      相关资源
      最近更新 更多