【问题标题】:Unicode filenames on Windows with Python & subprocess.Popen()使用 Python 和 subprocess.Popen() 在 Windows 上的 Unicode 文件名
【发布时间】:2009-12-15 20:45:14
【问题描述】:

为什么会出现以下情况:

>>> u'\u0308'.encode('mbcs')   #UMLAUT
'\xa8'
>>> u'\u041A'.encode('mbcs')   #CYRILLIC CAPITAL LETTER KA
'?'
>>>

我有一个 Python 应用程序接受来自操作系统的文件名。它适用于某些国际用户,但不适用于其他用户。

例如,这个 unicode 文件名: u'\u041a\u0433\u044b\u044b\u0448\u0444\u0442'

不会使用 Windows 'mbcs' 编码(文件系统使用的编码,由 sys.getfilesystemencoding() 返回)进行编码。我得到'???????',表示编码器在这些字符上失败。但这没有任何意义,因为文件名一开始就来自用户。

更新:这是我背后原因的背景... 我的系统上有一个以西里尔文命名的文件。我想用该文件作为参数调用 subprocess.Popen() 。 Popen 不会处理 unicode。通常我可以使用 sys.getfilesystemencoding() 给出的编解码器对参数进行编码。在这种情况下它不起作用

【问题讨论】:

  • 请添加有关 Popen 调用的信息:可执行文件是什么?是你写的吗?
  • 可执行文件由我的团队编写;我们已经通过将命令行编码为 utf-8 并让调用的可执行文件对其进行解码来解决此问题。 (正如下面 John Machin 所建议的那样。谢谢!)
  • 为了获得有效的答案,请发布有问题的代码的示例部分 (popen)。在任何情况下,调用 encode('mbcs') 都不会解决您的问题。任何将使用当前代码页的东西都不是有效的解决方案。

标签: python windows unicode


【解决方案1】:

在 Py3K 中——至少从 Python 3.2 开始——subprocess.Popensys.argv 在 Windows 上与(默认 unicode)字符串一致。显然使用了CreateProcessWGetCommandLineW

在 Python 中 - 至少到 v2.7.2 - subprocess.Popen 带有 Unicode 参数是错误的。它坚持CreateProcessA(而os.* 与Unicode 一致)。并且shlex.split 制造了额外的废话。

Pywin32 的win32process.CreateProcess 也不会自动切换到W 版本,也没有win32process.CreateProcessW。与GetCommandLine 相同。 因此需要使用ctypes.windll.kernel32.CreateProcessW...。 子流程模块可能应该解决这个问题。

argv[1:] 上的 UTF8 与私有应用程序在 Unicode 操作系统上仍然很笨拙。对于像 Linux 这样的 8 位“Latin1”字符串操作系统,这些技巧可能是合法的。

更新 vaab 为 Python 2.7 创建了一个补丁版本的Popen 来解决这个问题。
https://gist.github.com/vaab/2ad7051fc193167f15f85ef573e54eb9
有解释的博文:http://vaab.blog.kal.fr/2017/03/16/fixing-windows-python-2-7-unicode-issue-with-subprocesss-popen/

【讨论】:

  • 有一个未解决的错误:bugs.python.org/issue1759845 他们不会修复它。唯一的解决方案是迁移到 Python 3。
  • @AaronDigulla:我在 Python 2.7 上看到了至少两个基于此答案的解决方法,用于 subprocess' args 与 mbcs 编码之外的字符:1. 调用适合您的情况的 os.* 函数可能已经支持 Unicode 2。使用 Unicode 参数直接调用 CreateProcessW
  • @J.F.Sebastian:我指的是“可能应该修复子流程模块”。如果你想使用官方 API,那么 Python 3 是唯一的解决方案。也就是说,您有一个工作示例如何将subprocess.Popen 替换为CreateProcessW
  • @AaronDigulla:我已经发布了一个修复程序,该修复程序利用@J.F.Sebastian 的建议,通过ctypes 调用CreateProcessW(..),用于Windows 上的python 2.7。 check this
  • @vaab 发布一个我们可以投票的答案怎么样?
【解决方案2】:

免责声明:我是下面提到的修复程序的作者。

要使用 python 2.7 在 windows 上支持 unicode 命令行,您可以使用 this patchsubprocess.Popen(..)

情况

Windows 上对 unicode 命令行的 Python 2 支持非常差。

被严重窃听:

  • 从调用方(通过subprocess.Popen(..))向系统发出unicode命令行,

  • 并从被调用方读取当前命令行 unicode 参数(通过sys.argv),

在 Python 2 上已确认和 won't be fixed。这些在 Python 3 中已修复。

技术原因

在 Python 2 中,subprocess.Popen(..)sys.argv 的 windows 实现使用非 unicode 就绪的 windows 系统调用 CreateProcess(..)(请参阅 python code 和 MSDN doc of CreateProcess)并且不使用 GetCommandLineW(..) for @ 987654338@.

在 Python 3 中,subprocess.Popen(..) 的 windows 实现使用从 3.0 开始的正确 windows 系统调用 CreateProcessW(..)(参见 3.0 中的 code)和 sys.argv 使用从 @ 开始的 GetCommandLineW(..) 987654345@(见3.3中的code)。

如何解决

给定的patch 将利用ctypes 模块调用C 窗口 系统CreateProcessW(..)直接。它提出了一个新的固定Popen 对象,通过覆盖私有方法Popen._execute_child(..) 和私有函数_subprocess.CreateProcess(..) 来设置和使用来自windows 系统库的CreateProcessW(..),以尽可能模仿它在Python 中的完成方式@987654353 @。

如何使用

这个blog post explanation 演示了如何使用给定的补丁。它还显示了如何读取当前进程 sys.argvanother fix

【讨论】:

  • +1:我还没有测试过,但它看起来是一个可行的解决方案(from win_subprocess import Popen 作为替代品,启用Popen(u"unicode command", ...) where win_subprocess.py_
【解决方案3】:

Docs for sys.getfilesystemencoding() 表示对于 Windows NT 及更高版本,文件名本身就是 Unicode。如果你有一个有效的 unicode 文件名,为什么还要费心使用 mbcs 对其进行编码?

Docs for codecs module 说 mbcs 使用“ANSI 代码页”进行编码(根据用户的区域设置会有所不同),所以如果区域设置不使用西里尔字符,splat。

编辑:所以你的进程正在调用 subprocess.Popen()。如果您调用的进程在您的控制之下,那么这两个进程应该能够同意使用 UTF-8 作为 Unicode 传输格式。否则,您可能需要在 pywin32 邮件列表中询问。在任何情况下,请编辑您的问题以说明您对所调用流程的控制程度。

【讨论】:

  • 我的系统上有一个以西里尔文命名的文件。我想用该文件作为参数调用 subprocess.Popen() 。 Popen 不会处理 unicode。通常我可以使用 sys.getfilesystemencoding() 给出的编解码器对参数进行编码。在这种情况下,它将不起作用。
  • @Norman:请编辑您的问题以包含有关 subprocess.Popen() 的信息
【解决方案4】:

如果您需要传递现有文件的名称,那么传递 8.3 版本的 Unicode 文件名可能会更有可能成功。

你需要安装pywin32包,然后你可以这样做:

>>> import win32api
>>> win32api.GetShortPathName(u"C:\\Program Files")
'C:\\PROGRA~1'

我相信这些短文件名只使用 ASCII 字符,因此您应该能够将它们用作命令行的参数。

如果您还需要指定要创建的文件名,您可以使用 Unicode 文件名从 Python 预先创建大小为零的文件,并将文件的短名称作为参数传递。

更新:用户 bogdan 正确地说可以禁用 8.3 文件名生成(我也禁用了它,当我的笔记本电脑上有 Windows XP 时),所以你不能依赖它们。因此,在处理 NTFS 卷时,作为另一种更牵强的方法,可以将 Unicode 文件名hard link 转换为纯 ASCII 文件名;将 ASCII 文件名传递给外部命令,然后将其删除。

【讨论】:

  • 你不应该尝试使用 8.3 文件名,请记住这些是可选的,它们可能会丢失。为了加速文件系统,禁用 NTFS 短文件名生成是一种常见的做法。
  • 如果我可以反对您的第一句话:可以尝试使用 8.3 文件名,但不应不依赖它们。因此,我的“你可能会有更好的机会”。
【解决方案5】:

在 Python 3 中,不要对字符串进行编码。 Windows 文件名本机是 Unicode,Python 3 中的所有字符串都是 Unicode,Popen 使用 CreateProcess Windows API 函数的 Unicode 版本。

对于 Python 2.7,最简单的解决方案是使用第三方模块 https://pypi.org/project/subprocessww/。没有“内置”解决方案来获得完整的 Unicode 支持(独立于系统语言环境),Python 2.7 的维护者认为这是一个功能请求而不是错误修复,所以这不会改变。

有关事物为何如此的详细技术解释,请参阅其他答案。

【讨论】:

    猜你喜欢
    • 2019-09-30
    • 2016-02-24
    • 2015-08-07
    • 1970-01-01
    • 2014-04-23
    • 2013-09-01
    • 2023-04-05
    • 1970-01-01
    • 2013-08-08
    相关资源
    最近更新 更多