到目前为止,Python stdlib for Windows/multi-platform 中还没有有效的命令行拆分功能。 (2016 年 3 月)
子进程
所以简而言之,subprocess.Popen .call 等最好这样做:
if sys.platform == 'win32':
args = cmd
else:
args = shlex.split(cmd)
subprocess.Popen(args, ...)
在 Windows 上,shell 选项的任一值都不需要拆分,而在内部 Popen 只需使用 subprocess.list2cmdline 再次重新加入拆分参数:-)。
使用选项shell=True,shlex.split 在 Unix 上也不是必需的。
拆分与否,在 Windows 上启动 .bat 或 .cmd 脚本(与 .exe .com 不同)您需要明确包含文件扩展名 - 除非 shell=True。
关于命令行拆分的注意事项:
shlex.split(cmd, posix=0) 在 Windows 路径中保留反斜杠,但它不理解正确的引用和转义。完全不清楚 shlex 的 posix=0 模式有什么好处 - 但 99% 它肯定会吸引 Windows/跨平台程序员......
Windows API 公开ctypes.windll.shell32.CommandLineToArgvW:
解析一个 Unicode 命令行字符串并返回一个指针数组
到命令行参数,以及这样的参数计数,
类似于标准 C 运行时 argv 和 argc
价值观。
def win_CommandLineToArgvW(cmd):
import ctypes
nargs = ctypes.c_int()
ctypes.windll.shell32.CommandLineToArgvW.restype = ctypes.POINTER(ctypes.c_wchar_p)
lpargs = ctypes.windll.shell32.CommandLineToArgvW(unicode(cmd), ctypes.byref(nargs))
args = [lpargs[i] for i in range(nargs.value)]
if ctypes.windll.kernel32.LocalFree(lpargs):
raise AssertionError
return args
但是,CommandLineToArgvW 函数是伪造的 - 或者 与强制标准 C argv & argc 解析稍有相似:
>>> win_CommandLineToArgvW('aaa"bbb""" ccc')
[u'aaa"bbb"""', u'ccc']
>>> win_CommandLineToArgvW('"" aaa"bbb""" ccc')
[u'', u'aaabbb" ccc']
>>>
C:\scratch>python -c "import sys;print(sys.argv)" aaa"bbb""" ccc
['-c', 'aaabbb"', 'ccc']
C:\scratch>python -c "import sys;print(sys.argv)" "" aaa"bbb""" ccc
['-c', '', 'aaabbb"', 'ccc']
观看http://bugs.python.org/issue1724822 以了解 Python 库中未来可能添加的内容。 (“fisheye3”服务器上提到的功能实际上并不正确。)
跨平台候选函数
有效的 Windows 命令行拆分相当疯狂。例如。试试\ \\ \" \\"" \\\"aaa """" ...
我当前用于跨平台命令行拆分的候选函数是我为 Python lib 提案考虑的以下函数。它的多平台;它比执行单字符步进和流式传输的 shlex 快约 10 倍;并且还尊重与管道相关的字符(与 shlex 不同)。它列出了已经在 Windows 和 Linux bash 上进行的严格的真实 shell 测试,以及 test_shlex 的遗留 posix 测试模式。
对剩余错误的反馈感兴趣。
def cmdline_split(s, platform='this'):
"""Multi-platform variant of shlex.split() for command-line splitting.
For use with subprocess, for argv injection etc. Using fast REGEX.
platform: 'this' = auto from current platform;
1 = POSIX;
0 = Windows/CMD
(other values reserved)
"""
if platform == 'this':
platform = (sys.platform != 'win32')
if platform == 1:
RE_CMD_LEX = r'''"((?:\\["\\]|[^"])*)"|'([^']*)'|(\\.)|(&&?|\|\|?|\d?\>|[<])|([^\s'"\\&|<>]+)|(\s+)|(.)'''
elif platform == 0:
RE_CMD_LEX = r'''"((?:""|\\["\\]|[^"])*)"?()|(\\\\(?=\\*")|\\")|(&&?|\|\|?|\d?>|[<])|([^\s"&|<>]+)|(\s+)|(.)'''
else:
raise AssertionError('unkown platform %r' % platform)
args = []
accu = None # collects pieces of one arg
for qs, qss, esc, pipe, word, white, fail in re.findall(RE_CMD_LEX, s):
if word:
pass # most frequent
elif esc:
word = esc[1]
elif white or pipe:
if accu is not None:
args.append(accu)
if pipe:
args.append(pipe)
accu = None
continue
elif fail:
raise ValueError("invalid or incomplete shell string")
elif qs:
word = qs.replace('\\"', '"').replace('\\\\', '\\')
if platform == 0:
word = word.replace('""', '"')
else:
word = qss # may be even empty; must be last
accu = (accu or '') + word
if accu is not None:
args.append(accu)
return args