将Unicode文件名转换为Python subprocess.call()

10

我想使用带有Unicode文件名的subprocess.call(),这里是简化后的问题:

n = u'c:\\windows\\notepad.exe '
f = u'c:\\temp\\nèw.txt'

subprocess.call(n + f)

这会触发著名的错误:

UnicodeEncodeError: 'ascii'编解码器无法编码字符u'\xe8'

使用utf-8进行编码会产生错误的文件名,而mbcs会将带有重音符号的文件名作为new.txt传递。

我已经无法再阅读这个令人困惑的主题并且一直在原地打转。我在过去发现了很多不同问题的答案,所以我想加入并寻求帮助。

谢谢


根据您的操作系统,如果使用latin-1或cp1252作为编码方式会发生什么? - Kathy Van Stone
1
你是否指定了源文件的编码? - Humphrey Bogart

-- coding: utf-8 --

我有时会使用拉丁-1技巧,但在这种情况下不能使用:
  1. 我还需要其他不在拉丁-1中的字符。
  2. 不幸的是,它在子进程中不起作用 - 即使我使用相同的拉丁-1编码对两个字符串进行编码,也会引发相同的错误。
感谢所有答案。
- otrov
这是Python 2.x还是3.x?如果是2.x,也许你可以尝试在3.x上运行它。 - Craig McQueen
现在是2.6版本,虽然考虑过升级到3版本,但目前还没有计划。 - otrov
1
看起来Python 3应该已经支持使用subprocess.call()传递Unicode参数了(http://bugs.python.org/issue19264)。 - jfs
7个回答

8
我找到了一个很好的解决方法,虽然有些混乱,但是它能够正常工作。
subprocess.call将会使用自己的编码方式将文本传递到终端,这可能与终端期望的编码方式不同。因为你想要使其具有可移植性,所以需要在运行时知道机器的编码方式。
以下内容:
notepad = 'C://Notepad.exe'
subprocess.call([notepad.encode(sys.getfilesystemencoding())])

尝试确定当前编码,然后将正确的编码应用于subprocess.call。

顺便提一下,如果您尝试使用当前目录组合字符串,使用

os.cwd() 

Python(或操作系统,我不确定)会在带有重音字符的目录中出现问题。为了防止这种情况发生,我找到了以下方法:

os.cwd().decode(sys.getfilesystemencoding())

这个解决方案与上面的解决方案非常相似。

希望能对您有所帮助。


OP说:“mbcs将文件名作为new.txt传递,没有重音符号”。mbcs是Windows上的sys.getfilesystemencoding(),即在这种情况下.encode(sys.getfilesystemencoding())无法工作。 - jfs
@J.F.Sebastian,我们看到的OP不一样;) 文件nèw.txt带有重音符号。 - Kpym
@Kpym:这是问题中的直接引用,意思是使用Windows ANSI代码页(mbcs)对带有重音符号的Unicode名称(nèw.txt)进行编码可能会且确实会在OP的系统上丢失重音符号,例如u'nèw.txt'.encode('ascii', 'ignore') -> b'new.txt'(实际代码页不是ASCII)。 - jfs

6
如果文件存在,您可以使用短文件名(也称为8.3名称)。该名称已定义为现有文件,并且在作为参数传递时不应对非Unicode感知程序造成任何问题。
获取一个的方法之一(需要安装Pywin32):
import win32api
short_path = win32api.GetShortPathName(unicode_path)

或者,您也可以使用ctypes

import ctypes
import ctypes.wintypes

ctypes.windll.kernel32.GetShortPathNameW.argtypes = [
    ctypes.wintypes.LPCWSTR, # lpszLongPath
    ctypes.wintypes.LPWSTR, # lpszShortPath
    ctypes.wintypes.DWORD # cchBuffer
]
ctypes.windll.kernel32.GetShortPathNameW.restype = ctypes.wintypes.DWORD

buf = ctypes.create_unicode_buffer(1024) # adjust buffer size, if necessary
ctypes.windll.kernel32.GetShortPathNameW(unicode_path, buf, len(buf))

short_path = buf.value

2
呵呵,那真是太恶心了!不过很方便。 - jambox

1

看起来要使这个工作,子进程代码必须被修改为使用宽字符版本的CreateProcess(假设存在这样一个版本)。有一个PEP讨论了同样的更改,针对文件对象在http://www.python.org/dev/peps/pep-0277/。也许你可以研究一下Windows C调用,并提出类似的更改来处理子进程。


我不觉得自己有能力研究这个问题,但很有趣的是看到它的作者(Neil)刚刚发布了支持Unicode(宽字符)文件名访问的SciTE 2.10。 - otrov

0

我没有答案,但我已经对这个问题进行了相当多的研究。Python将所有输出(包括系统调用)转换为运行终端的相同字符。Windows终端使用代码页进行字符映射;默认代码页是437,但可以使用chcp命令更改。chcp 65001理论上会将代码页更改为utf-8,但据我所知,Python不知道该怎么做,所以你只能自求多福。


0

您可以尝试以下方式打开文件:

subprocess.call((n + f).encode("cp437"))

或者使用命令提示符窗口报告的任何代码页chcp。如果您尝试像starbuck建议的那样执行chcp 65001,则必须先编辑stdlib encodings\aliases.py文件并将cp65001添加为“utf-8”的别名。这是Python源代码中的一个未解决问题。

更新:由于这是多目标场景,运行此类命令之前,请确保首先运行单个chcp命令,分析输出并检索当前的“命令提示符”(DOS)代码页。随后,使用发现的代码页对subprocess.call参数进行编码。


我使用的是cp1251编码,但程序应该在不同的机器上以任意语言环境运行。 - otrov
cp1251是Windows代码页。在使用subprocess运行命令时,需要使用“DOS”/命令提示符代码页。 - tzot
@tzot:除非您指的是mbcs编码(您可以使用locale.getpreferredencoding()查看其值),否则它是不正确的。而且,OP已经说过他的系统上的mbcs不支持所需的字符。chcp可能会返回不同的编码。 - jfs

0

正如ΤΖΩΤΖΙΟΥ和starbuck所提到的,问题出在控制台代码页上,对于你的情况是866(Windows的俄语本地化),而不是1251。只需在控制台中运行chcp即可。

当你想要将Unicode输出到Windows控制台时,问题是相同的。不幸的是,你需要至少在encodings\aliases.py中注册并别名为'cp866'(或在脚本启动时以编程方式完成),并在运行记事本并设置后将控制台的代码页更改为65001。

chcp 65001 & c:\WINDOWS\notepad.exe nèw.txt & chcp 866

顺便提一下,为了能够在控制台中运行命令并正确地查看文件名,您需要将控制台字体更改为控制台窗口属性中的Lucida Console。
情况可能会更糟:您需要更改当前进程的代码页。为此,您需要在脚本启动之前运行chcp 65001或使用pywin32在脚本内部执行它。

谢谢大家的努力,非常感激 :)不幸的是我不能让它工作。传递给subprocess()或更准确地说是CreateProcess()的字符串被打印为“chcp 65001 & c:\windows\notepad.exe nèw.txt”,导致错误“系统找不到指定的文件”。也许我做错了,但我尝试了我所理解的。我在当前的cp中没有问题将unicode文件名粘贴到Windows控制台中,可以在这里看到:http://img402.imageshack.us/img402/9875/sshot1x.png - otrov

0

使用os.startfile和操作edit。这样做效果更好,因为它会打开您的扩展名的默认应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接