python,windows:用shlexparsing命令行

当你必须拆分一个命令行时,例如调用popen,最好的做法似乎是

subprocess.Popen(shlex.split(cmd), ...

但是RTFM

shlex类可以很容易地编写出类似于Unix shell的简单语法的词法分析器。

那么,win32上的正确方法是什么? 那么报价parsing和POSIX与非POSIX模式呢? 最好的问候,马西莫

到目前为止,在Windows / multi-platform的Python stdlib中没有有效的命令行分割功能。 (2016年3月)

所以简而言之, subprocess.Popen .call等最好做如下:

 if sys.platform == 'win32': args = cmd else: args = shlex.split(cmd) subprocess.Popen(args, ...) 

在Windows上,对于shell选项中的任何一个值, 拆分都是不必要subprocess.list2cmdline只是使用subprocess.list2cmdline来重新加入拆分参数:-)。

使用选项shell=Trueshlex.split在Unix上shlex.split是必需的。

在Windows上用于启动.bat.cmd脚本(与.exe .com不同),您需要明确地包含文件扩展名,除非shell=True

命令行拆分注意事项:

shlex.split(cmd, posix=0)在Windows路径中保留反斜杠,但不能理解引用和转义权限。 它不是很清楚什么shix的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