Python 的子流程不允许使用 shell=True 进行流程替换?

Process substitution not allowed by Python's subprocess with shell=True?

这是一个进程替换的玩具示例,在 Bash 中运行良好:

$ wc -l <(pwd)
1 /proc/self/fd/11

那么,为什么从 Python 的子进程调用 shell=True 时,相同的命令会出现语法错误?

>>> subprocess.check_call('wc -l <(pwd)', shell=True)
/bin/sh: 1: Syntax error: "(" unexpected
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/my/python/lib/python3.5/subprocess.py", line 581, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'wc -l <(pwd)' returned non-zero exit status 2

/bin/sh: 1: Syntax error: "(" unexpected

你有一个bashism。根据 POSIX,这是无效的,这是 /bin/sh 实现的。

另一种解决方案是将更多 shell 代码转移到 Python 本身。例如:

from subprocess import Popen, PIPE, check_call

p1 = Popen(["pwd"], stdout=PIPE)
p2 = check_call(["wc", "-l"], stdin=p1.stdout)

这通常是消除使用 subprocess 的第一步,因为它将工作分解为更小的块,您可能更容易在 Python 中看到如何做本身。

如果你想使用Bash特性(数组,命令替换,这里是字符串,or a lot of other non-POSIX extensions and enhancements),你需要显式覆盖默认的shell:

subprocess.check_call(
    'wc -l <(pwd)',
    executable='/bin/bash',  # the beef
    shell=True)

或者 - 更笨拙 - 运行 一个明确的 Bash 实例:

subprocess.check_call(
    ['/bin/bash', '-c', 'wc -l <(pwd)'])

注意在后一种情况下我们如何避免单独指定 shell=True,并将脚本作为字符串列表传递(其中第三个字符串是任意复杂的 and/or 长脚本作为参数至 bash -c).

(实际上有一个长度限制。如果你的命令行比内核常量长 ARG_MAX 你需要将脚本传递到文件中或作为标准输入到 shell相反。在任何现代系统上,我们都在谈论兆字节的脚本。)

无论如何,

运行 复杂的 shell 脚本(Bash 或其他)是可疑的;您将希望尽可能少地委派给 subprocess 并以原生 Python 代码从那里获取它。