使用Python和subprocess.Popen()在Windows上处理Unicode文件名
为什么会出现以下情况:
>>> 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 个回答
关于sys.getfilesystemencoding()的文档提到,在Windows NT及更高版本中,文件名本身就是使用Unicode编码的。如果你有一个有效的Unicode文件名,为什么还要用mbcs进行编码呢?
关于codecs模块的文档说明,mbcs使用的是“ANSI代码页”进行编码(这会根据用户的地区设置而不同),所以如果你的地区设置不支持西里尔字母,那就会出问题。
补充说明:你的程序正在调用subprocess.Popen()。如果你调用的进程是你自己控制的,那么这两个进程应该可以达成一致,使用UTF-8作为Unicode传输格式。否则,你可能需要在pywin32的邮件列表上询问。在任何情况下,请修改你的问题,说明你对被调用进程的控制程度。
免责声明:我是下面提到的修复的作者。
为了在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.argv
从3.3
开始使用GetCommandLineW(..)
(可以查看3.3
的代码)。
如何修复
这个补丁将利用ctypes
模块直接调用C语言的Windows系统函数CreateProcessW(..)
。它通过重写私有方法Popen._execute_child(..)
和私有函数_subprocess.CreateProcess(..)
来设置和使用CreateProcessW(..)
,尽量模拟Python 3.6
中的实现方式。
如何使用
在Python 3K中,至少从Python 3.2开始,subprocess.Popen
和sys.argv
在Windows上使用(默认的Unicode)字符串时表现得很一致。显然,它们使用了CreateProcessW
和GetCommandLineW
这两个函数。
而在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/