12

当您必须拆分命令行时,例如 call Popen,最佳实践似乎是

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

但RTFM

该类shlex使为类似于 Unix shell 的简单语法编写词法分析器变得容易...

那么,win32上的正确方法是什么?那么报价解析和 POSIX 与非 POSIX 模式呢?

4

1 回答 1

29

到目前为止,Windows/多平台的 Python 标准库中还没有有效的命令行拆分功能。(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=Truetheshlex.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. 对有关剩余错误的反馈感兴趣。

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
于 2016-03-09T18:46:37.553 回答