python, windows : 使用 shlex 解析命令行

python, windows : parsing command lines with shlex

当您必须拆分命令行时,例如调用 Popen,最佳做法似乎是

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

但是 RTFM

The shlex class makes it easy to write lexical analyzers for simple syntaxes resembling that of the Unix shell ...

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

到目前为止,Windows/multi-platform 的 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=Trueshlex.split 在 Unix 上也不是必需的。

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

关于命令行拆分的注意事项:

shlex.split(cmd, posix=0) 在 Windows 路径中保留反斜杠,但它不理解正确的引用和转义。尚不清楚 shlex 的 posix=0 模式到底有什么用 - 但 99% 它肯定会引诱 Windows/cross-platform 程序员 ...

Windows API 公开 ctypes.windll.shell32.CommandLineToArgvW:

Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.

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 解析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 执行单字符步进和流式处理;并且还尊重与管道相关的字符(与 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