【问题标题】:Decoding filename issue解码文件名问题
【发布时间】:2013-10-08 02:06:36
【问题描述】:

为了简化我的问题,我创建了一个工作演示,它应该根据处理文件名的 python unicode 文档工作。输出如下:

$ ./test_unicode.py /tmp/gsynctest/Greg.*
p = '/tmp/gsynctest/Greg. Descripci\xf3n v\xeddeos'
up = u'/tmp/gsynctest/Greg. Descripci\xf3n v\xeddeos'
up.utf8 = /tmp/gsynctest/Greg. Descripción vídeos
Command line file exists = True
Unicode file exists = False
UTF-8 file exists = False

如您所见,按照出现的顺序,p 是通过 argv 和 glob 提供的文件名。尽管我的终端具有 LANG="en_GB.UTF-8",但它具有“latin-1”编码。如果我使用严格的 unicode 错误集对其进行解码,我会得到up 显示的字符串。如果我将其编码为 utf8,我会得到代表真实文件名的内容。

但是,根据 unicode 文档,应使用 sys.getfilesystemencoding() 对 unicode 文件名进行编码以便访问它。但这不起作用。三个 exists 检查显示哪一个有效,它似乎是 latin-1 (ISO-8859-1) 编码。

我不知道为什么我看到的内容不反映文档。

这里是测试程序代码:

#!/usr/bin/env python

import sys, os

paths = sys.argv[1:]

fsenc = sys.getfilesystemencoding()

for p in paths:
    print "p = %s" % repr(p)

    if not isinstance(p, unicode):
        up = unicode(p, encoding = "latin-1", errors = "strict")

    print "up = %s" % repr(up)
    print "up.utf8 = %s" % up.encode("utf8")

    print "Command line file exists = %s" % os.path.exists(p)
    print "Unicode file exists = %s" % os.path.exists(up)
    print "%s file exists = %s" % (fsenc, os.path.exists(up.encode(fsenc)))

。 . .

原问题:

如果我尝试以原始形式解码以下文件名表示,我会收到“无效的连续字节”错误:Greg. Descripci\xf3n v\xeddeos\n

for p in paths:
    p = p.decode(sys.getfilesystemencoding())

这是提交此错误的用户提交的真实文件名。我对 unicode / UTF-8 编码的理解不是很好,但据我所知,它不是合法的 UTF-8,因为它需要某种终结符。我并不关心打印时文件名的外观,它只需要在磁盘上可以访问。处理这样的文件的常规方法是什么?我的大部分问题都源于尝试打印文件:

debug(u"Filename: %s" % unicode(path))

更新:尝试、更努力、更努力仍然有什么好处吗?

for e in (sys.getfilesystemencoding(), "UTF-8", "Latin-1"):
    try:
        p_dec = p.decode("Latin-1")
        p = p_dec.encode(sys.getfilesystemencoding())
    except UnicodeDecodeError:
        pass

对于文件系统编码相同的编码,显然不是最佳选择,因为它将以相同的编码进行解码和编码。但至少我可以保证在后续调用中解码文件名不会出现异常。我看到的唯一问题是,不正确的编码可能会正确解码文件名,从而产生完全错误的编码文件名。

无论哪种方式,我都需要跟踪两个文件名吗?磁盘上可访问的原始文件名和可打印文件名?还是文件系统编码的文件名既可打印又可访问?

更新 2:我的问题的答案是“否”。我实现了自己的编解码器来循环编码类型并在文件系统编码中重新编码。该表示现在可打印:Greg. Descripción vídeos,但该文件不再可访问。所以我认为保持文件系统访问和可打印性的最简单方法是将文件名包装在一个具有打印和 IO 实现的类中;除非有人有任何其他建议?

【问题讨论】:

  • UTF-8 不会“期待某种终结符”。但是i 之后的\xf3 不是任何有效的UTF-8 字符串的一部分。这几乎可以肯定是 Latin-1 或相关的东西,比如 cp1252。
  • 另外,从您的描述中不清楚哪一行正在获取此字符串并引发此异常。是p.decode(sys.getfilesystemencoding()),还是unicode(path)
  • 什么操作系统?在 Windows 上,使用 unicode 字符串作为文件名,以避免 C stdio 函数的代码页限制字节文件名处理。
  • Linux。看来p 毕竟是可打印的。 p 在另一个函数中发生了一些事情,该函数通过将其包装在 unicode() 中来对其进行转换。这是一个简单的解决方法。然后另一个异常是由于从文件路径构造的正则表达式在调试语句中不可打印的结果。我通过包装repr() 语句解决了这个问题。我得到了不可打印的字符,但我并不太担心这些,我只是希望它不会引发异常。
  • 最后,如果从 Latin-1 解码,然后编码为 UTF-8 以进行打印会产生可读的输出,这意味着您的终端是 UTF-8,不是 Latin-1 .这意味着只执行ls /tmp/gsynctest/Greg* 应该显示mojibake,因为您拥有的以拉丁语1 命名的文件名作为UTF-8 是无意义的。但是,一些 UTF-8 解码器比 Python 更宽松,并尝试将任何无效字节视为 Latin-1,因此您的测试用例实际上可能看起来仍然有效,这使得调试变得更加困难。

标签: python unicode utf-8 codec


【解决方案1】:

字符串 Greg. Descripci\xf3n v\xeddeos\n 以 latin-1 或其他一些非 Unicode 和非 UTF-8 编码进行编码,因此您需要这样做:

"Greg. Descripci\xf3n v\xeddeos\n".decode('latin-1').encode(sys.getfilesystemencoding())

这会产生:

'Greg. Descripci\xc3\xb3n v\xc3\xaddeos\n'

问题是您自己的文件系统编码可能与用于通过网络提交文件或文件名的编码不匹配。您可能需要检查传入的编码以查看实际编码是什么。它可能是也可能不是 latin-1:我只使用了 latin-1,因为它是最通用的 8 位编码。

(取决于你在做什么,你可能实际上不需要重新编码。)

【讨论】:

    【解决方案2】:

    首先,只写unicode(path) 几乎总是一个坏主意。如果需要将字符串转换为 Unicode,则需要知道它的字符集是什么。

    假设p 表示来自文件系统的路径(例如,你从os.listdir 得到它),那么你想用文件系统的编码来解码它,而不仅仅是Python 认为是一个不错的默认值。* 所以,正确的做法是您在上面已经做过的事情:

    p = p.decode(sys.getfilesystemencoding())
    

    如果path 代表其他东西(例如,您从用户输入中得到它),那就另当别论了。

    或者,如果path 是您在上面计算的那些p 值之一,那么它已经 unicode,因此尝试再次对其进行解码将重新编码为你的默认编码,然后重新解码,这是一件愚蠢的事情。

    但如果不知道字符串的来源,您(和我们)无法知道它在什么字符集中,因此您无法知道如何对其进行解码。


    * 在某些系统上,你会很幸运。例如,对于 Mac 上的 Python 3.x,默认编码和文件系统编码都将始终为 UTF-8。但是在较旧的 linux 机器上使用 Python 2.x,默认编码可能是 UTF-8,而文件系统是 Latin-1……这似乎正是您在这里得到的。

    【讨论】:

    • 它既是文件系统上的路径,又是作为参数提供的。为了重现该问题,我在用户拥有该文件时创建了该文件。然后,我在命令行上使用了一个 glob 将其作为参数提供:myprog /path/to/Greg*。我上面提供的代码出现了两个不同的错误。 unicode(path) 产生臭名昭著的“序数不在范围内”异常。我知道你不能只指望 unicode() 能弄明白,尽管那样好。 unicode() 调用只是我修改为debug("File: %s" % repr(path)) 的现有代码的一个示例。这样就剩下 decode() 了。
    • @Craig:“它是文件系统上的路径”并不重要,重要的是你是从文件系统中得到它,还是从用户输入(或某处否则,就像来自另一台机器的 JSON RPC 调用)。来自sys.argv[1]raw_input 的东西将出现在用户终端的编码中,如果Python 能够找出它,它将是sys.stdin.encoding,但不是sys.getfilesystemencoding()(除非你很幸运,因为他们的文件系统和终端碰巧使用相同的编码——保证在 Mac 上,但不是大多数其他 POSIX 系统。
    • 好的,输入时提供的初始路径是终端编码的。但是,这些路径由 os.walk() 遍历以递归处理它们。所以一些路径是文件系统编码的,其他路径是终端的。实际上很容易区分哪些是哪些。问题是,当我在解码后以文件系统编码对文件名进行编码时,它不再可访问。如果我将文件名保留为本机形式,则可以访问它。但是在本机形式中,它不能使用 os.path.join 与其他任何东西连接。剩下的唯一合理的解决方案是包装器。
    • “不再可访问”是什么意思?同时,作为一般规则,尽快解码,尽可能长时间保持 Unicode,尽可能晚地编码。你可以在unicode 字符串上使用os.path,只要你一直这样做(不要尝试joinstr 和`unicode`)。无论如何,如果你给我们一个完整的例子——带有完整描述的测试用例的可运行代码——而不是一次一点点的信息,那将会有所帮助。
    • 可访问:for p in path: p 可访问。 p.decode("latin-1").encode(sys.getfilesystemencoding()) 不可访问,即找不到文件。
    猜你喜欢
    • 1970-01-01
    • 2021-05-13
    • 2013-01-28
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 2011-05-23
    • 1970-01-01
    相关资源
    最近更新 更多