使用Python和subprocess.Popen()在Windows上处理Unicode文件名

13 投票
5 回答
7809 浏览
提问于 2025-04-15 17:04

为什么会出现以下情况:

>>> 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()给出的编码来处理这个参数,但在这种情况下却不行。

5 个回答

3

关于sys.getfilesystemencoding()的文档提到,在Windows NT及更高版本中,文件名本身就是使用Unicode编码的。如果你有一个有效的Unicode文件名,为什么还要用mbcs进行编码呢?

关于codecs模块的文档说明,mbcs使用的是“ANSI代码页”进行编码(这会根据用户的地区设置而不同),所以如果你的地区设置不支持西里尔字母,那就会出问题。

补充说明:你的程序正在调用subprocess.Popen()。如果你调用的进程是你自己控制的,那么这两个进程应该可以达成一致,使用UTF-8作为Unicode传输格式。否则,你可能需要在pywin32的邮件列表上询问。在任何情况下,请修改你的问题,说明你对被调用进程的控制程度。

5

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

为了在Windows上使用Python 2.7支持Unicode命令行,你可以使用这个补丁来修复subprocess.Popen(..)

情况说明

在Windows上,Python 2对Unicode命令行的支持非常差。

存在严重的bug:

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

  • 以及从被调用方(通过sys.argv)读取当前命令行的Unicode参数,

这些问题已经被确认,并且在Python 2中不会被修复。这些问题在Python 3中得到了修复。

技术原因

在Python 2中,Windows上subprocess.Popen(..)sys.argv的实现使用了不支持Unicode的Windows系统调用CreateProcess(..)(可以查看Python的代码,以及MSDN上关于CreateProcess文档),而没有使用GetCommandLineW(..)来处理sys.argv

在Python 3中,Windows上subprocess.Popen(..)的实现从3.0开始使用了正确的Windows系统调用CreateProcessW(..)(可以查看3.0代码),而sys.argv3.3开始使用GetCommandLineW(..)(可以查看3.3代码)。

如何修复

这个补丁将利用ctypes模块直接调用C语言的Windows系统函数CreateProcessW(..)。它通过重写私有方法Popen._execute_child(..)和私有函数_subprocess.CreateProcess(..)来设置和使用CreateProcessW(..),尽量模拟Python 3.6中的实现方式。

如何使用

如何使用这个补丁可以参考这篇博客文章。它还展示了如何使用另一个修复来读取当前进程的sys.argv

8

在Python 3K中,至少从Python 3.2开始,subprocess.Popensys.argv在Windows上使用(默认的Unicode)字符串时表现得很一致。显然,它们使用了CreateProcessWGetCommandLineW这两个函数。

而在Python 2.7.2及之前的版本中,subprocess.Popen在处理Unicode参数时有问题。它依然使用CreateProcessA(而os.*则能很好地处理Unicode)。另外,shlex.split还会产生一些额外的混乱。

Pywin32的win32process.CreateProcess也不会自动切换到W版本,而且没有win32process.CreateProcessW这个函数。GetCommandLine也是如此。因此,需要使用ctypes.windll.kernel32.CreateProcessW...。关于这个问题,subprocess模块可能需要修复。

在Unicode操作系统上,使用UTF8的argv[1:]在私有应用中仍然显得笨拙。这种做法在像Linux这样的8位“Latin1”字符串操作系统上可能是合法的。

更新 vaab创建了一个修补版本的Popen,适用于Python 2.7,解决了这个问题。
可以查看这个链接:https://gist.github.com/vaab/2ad7051fc193167f15f85ef573e54eb9
还有一篇博客文章解释了这个问题:http://vaab.blog.kal.fr/2017/03/16/fixing-windows-python-2-7-unicode-issue-with-subprocesss-popen/

撰写回答