将多个子进程连接起来

14

我有5个进程 p1,p2,...,p5,我想将一些数据写入p1的标准输入,将p1的输出导向到p2的标准输入,最后从p5的输出中读取最终结果。

我到目前为止尝试过:

p1 = Popen(['p1'], stdin=PIPE, stdout=PIPE)
p2 = Popen(['p2'], stdin=p1.stdout, stdout=PIPE)
...
p5 = Popen(['p5'], stdin=p4.stdout, stdout=PIPE)

# write data to stdin
p1.stdin.write(indata)
p1.stdin.close()

# not sure in what order to close the pipes here, if at all

# read output
out = p5.stdout.read()
print out

最后的代码片段卡住了,因为我可能没有正确执行读/写操作。

我能够使用 communicate()获取单个进程,并且在不向第一个进程提供任何输入的情况下获得两个进程(来自Python文档的示例)。

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]

但我无法弄清楚如何在不挂起解释器的情况下向第一个进程提供输入。

我也可以使用一个Bash脚本来实现这个目标(我已经编写了工作正常的脚本),但我想知道如何用Python实现相同的功能。

因此,我想问,在管道中正确地执行所有操作的顺序是什么,具体来说,在管道上做读/写/关闭操作的顺序是什么?

我正在64位Linux上工作,如果有影响的话。

编辑:我忘记提到所有的进程p1、..p5都会消耗掉它们得到的所有输入,处理完后写入stdout并终止。因此,在前一个进程完成处理之前,管道中的后续进程不应该终止。

编辑2:我知道我也可以使用

command = 'bash -c "p1 | p2 | p3 | p4 | p5"'
proc = Popen([command], shell=True)
out, err = proc.communicate(input=indata)
print out

但我的主要兴趣是了解如何在纯Python代码中链式使用管道。


这里有一个相关的问题:https://dev59.com/b3VC5IYBdhLWcg3wbglT。看起来不需要使用显式临时文件(如此处的接受答案中),但是,似乎没有任何直接的、纯Pythonic的方法来做到这一点,这让我感到惊讶。Plumbum(在其中一个答案中提到)看起来还不错,但对我来说太过“神奇”了(这是Python,不是Perl!)。使用`subprocess`完全实现这一点的方法(例如http://sam.nipl.net/code/python/pipeline.py,它来自另一个答案的评论)似乎容易出现奇怪的错误。 - Kyle Strand
实际上,我刚刚发现了pipes模块(https://docs.python.org/2/library/pipes.html),并相应地为另一个问题添加了答案。它看起来比其他解决方案要好得多。 - Kyle Strand
1个回答

10

也许这能帮到你:

import sys
import tempfile
from subprocess import Popen, PIPE


cmd = [sys.executable, '-c', 'print raw_input()']

# Using a temp file to give input data to the subprocess instead of stdin.write to avoid deadlocks.
with tempfile.TemporaryFile() as f:
    f.write('foobar')
    f.seek(0)  # Return at the start of the file so that the subprocess p1 can read what we wrote.
    p1 = Popen(cmd, stdin=f, stdout=PIPE)

p2 = Popen(cmd, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd, stdin=p2.stdout, stdout=PIPE)

# No order needed.
p1.stdout.close()
p2.stdout.close()

# Using communicate() instead of stdout.read to avoid deadlocks. 
print p3.communicate()[0]

输出:

$ python test.py
foobar

希望这对你有所帮助。


谢谢你的解决方案。它非常聪明并且有效。如果我现在理解正确,那么没有办法在没有真实文件描述符的情况下进行输入?例如,使用StringIO文件对象是不起作用的,因为没有fileno? - Timo
@Timo:是的,你需要一个具有fileno的真实文件,很高兴它有帮助:) - mouad
呵呵,我一看到这个问题就想,“我肯定答案会涉及文件I/O什么的……” - JAB
@JAB:是的,subprocess模块并没有给我们太多选择 :) - mouad
Python 3 注意事项:print(input())f.write(b'foobar')(或者 with tempfile.TemporaryFile('w'))。话虽如此,我在我的脚本中无法完全检查它是否有效,因为在我的情况下,我连续两次使用 cmd=["gsettings", "set", gsettings_schema, gsettings_key, value] 在 Ubuntu 上设置配置值,但最终值有时是第一个,有时是第二个。 - hsandt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接