之前的答案错过了一个重要的点。正如 geocar 所指出的,Replacing shell pipeline 基本上是正确的。 几乎在管道的最后一个元素上运行 communicate 就足够了。
剩下的问题是将输入数据传递给管道。对于多个子进程,最后一个元素上的简单 communicate(input_data) 不起作用 - 它永远挂起。您需要像这样手动创建一个管道和一个子级:
import os
import subprocess
input = """\
input data
more input
""" * 10
rd, wr = os.pipe()
if os.fork() != 0: # parent
os.close(wr)
else: # child
os.close(rd)
os.write(wr, input)
os.close(wr)
exit()
p_awk = subprocess.Popen(["awk", "{ print $2; }"],
stdin=rd,
stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"],
stdin=p_awk.stdout,
stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())
现在子进程通过管道提供输入,父进程调用communicate(),按预期工作。使用这种方法,您可以创建任意长的管道,而无需求助于“将部分工作委托给 shell”。不幸的是,subprocess documentation 没有提到这一点。
有一些方法可以在没有管道的情况下达到同样的效果:
from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)
现在将stdin=tf 用于p_awk。看你喜欢什么口味了。
上面仍然不是 100% 等效于 bash 管道,因为信号处理不同。如果添加另一个截断sort 输出的管道元素,您可以看到这一点,例如head -n 10。使用上面的代码,sort 将向stderr 打印“Broken pipe”错误消息。当您在 shell 中运行相同的管道时,您不会看到此消息。 (这是唯一的区别,stdout 中的结果是相同的)。原因似乎是python的Popen将SIG_IGN设置为SIGPIPE,而shell将其留在SIG_DFL,而sort的信号处理在这两种情况下是不同的。