如何使用subprocess.Popen通过管道连接多个进程?

时间:2008-11-17 12:17:49

标签: python pipe subprocess

如何使用Python subprocess模块执行以下shell命令?

echo "input data" | awk -f script.awk | sort > outfile.txt

输入数据将来自字符串,因此我实际上不需要echo。我已经走到这一步了,任何人都可以解释我是如何通过sort管道的吗?

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

更新:请注意,虽然下面接受的答案实际上没有回答问题,但我相信S.Lott是对的,最好避免首先解决这个问题!

9 个答案:

答案 0 :(得分:38)

你会对以下内容感到高兴。

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )

将部分工作委托给shell。让它用管道连接两个进程。

你会更乐意将'script.awk'改写成Python,消除awk和管道。

修改即可。提出awk没有帮助的一些原因。

[有太多理由通过评论回复。]

  1. Awk正在增加一个没有重大价值的步骤。有关Python无法处理的awk处理没有什么独特之处。

  2. 对于大型数据集,从awk到sort的流水线操作可以提高处理时间。对于短数据集,它没有显着的好处。快速测量awk >file ; sort fileawk | sort将揭示并发性有帮助。使用sort,它很少有帮助,因为sort不是一次性过滤器。

  3. “Python to sort”处理的简单性(而不是“Python to awk to sort”)可以防止在这里提出确切的问题。

  4. Python - 虽然比awk更冗长 - 也是明确的,其中awk具有某些隐含的规则,这些规则对新手不透明,并且对非专业人员造成混淆。

  5. Awk(就像shell脚本本身一样)添加了另一种编程语言。如果所有这些都可以用一种语言(Python)完成,那么消除shell和awk编程就会消除两种编程语言,从而允许某人专注于任务的价值生成部分。

  6. 底线:awk无法添加重要价值。在这种情况下,awk是净成本;它增加了足够的复杂性,有必要提出这个问题。删除awk将是净收益。

    补充工具栏为什么构建管道(a | b)非常困难。

    当shell遇到a | b时,必须执行以下操作。

    1. 分叉原始shell的子进程。这最终将成为b。

    2. 构建一个os管道。 (不是Python subprocess.PIPE)但是调用os.pipe(),它返回两个通过公共缓冲区连接的新文件描述符。此时,进程有来自其父级的stdin,stdout,stderr,以及一个将是“a stdout”和“b's stdin”的文件。

    3. 分叉一个孩子。孩子用新的标准替换它的标准输出。执行a流程。

    4. b子关闭用新b的标准输入替换其标准输入。执行b流程。

    5. b孩子等待a完成。

    6. 父母正在等待b完成。

    7. 我认为上面的内容可以递归地用于生成a | b | c,但是您必须隐式地将长管道括起来,将它们视为a | (b | c)

      由于Python有os.pipe()os.exec()os.fork(),您可以替换sys.stdinsys.stdout,因此有一种方法可以在纯Python中执行上述操作。实际上,您可以使用os.pipe()subprocess.Popen制作一些快捷方式。

      但是,将该操作委托给shell更容易。

答案 1 :(得分:22)

import subprocess

some_string = b'input_data'

sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                 stdin=subprocess.PIPE).communicate(some_string)

答案 2 :(得分:17)

模拟shell管道:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', shell=True)

不调用shell(参见17.1.4.2. Replacing shell pipeline):

#!/usr/bin/env python
from subprocess import Popen, PIPE

a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
    with a.stdout, open("outfile.txt", "wb") as outfile:
        b = Popen(["b"], stdin=a.stdout, stdout=outfile)
    a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already

plumbum提供了一些语法糖:

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

类似于:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

是:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()

答案 3 :(得分:3)

http://www.python.org/doc/2.5.2/lib/node535.html非常清楚。你有什么不明白的一部分吗?

你的程序非常相似,但第二个Popen会有stdout =到一个文件,你不需要它的.communicate()的输出。

答案 4 :(得分:2)

受@ Cristian的回答启发。我遇到了同样的问题,但是使用了不同的命令。所以我把我测试的例子,我认为可能有用:

grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()

这是经过测试的。

做了什么

  • 使用管道中的stdin声明延迟grep执行。当管道将填充ps的标准输出时,将在ps命令执行时执行此命令。
  • 将主要命令ps调用,并将stdout定向到grep命令使用的管道。
  • Grep传达了从管道中获取stdout。

我喜欢这种方式,因为它是用subprocess界面轻轻包裹的天然管道构思。

答案 5 :(得分:2)

可接受的答案绕开了问题。 这是一个链接多个进程的输出的代码段: 请注意,它还会打印(有点)等效的shell命令,以便您可以运行它并确保输出正确。

#!/usr/bin/env python3

from subprocess import Popen, PIPE

# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")

p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)

print("Output from last process : " + (p3.communicate()[0]).decode())

# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)

答案 6 :(得分:1)

编辑: pipes在Windows上可用,但至关重要的是,在Windows上实际上工作似乎并不存在。见下面的评论。

Python标准库现在包含用于处理此问题的pipes模块:

https://docs.python.org/2/library/pipes.htmlhttps://docs.python.org/3.4/library/pipes.html

我不知道这个模块已经存在了多长时间,但这种方法似乎比使用subprocess更简单。

答案 7 :(得分:1)

以前的答案错过了重要的一点。正如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())

现在,子进程通过管道提供输入,父进程调用communic(),它按预期工作。使用这种方法,您可以创建任意长的管道,而无需“将部分工作委托给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的PopenSIG_IGN设置SIGPIPE,而shell将其保留在SIG_DFL,而sort的信号处理在这两个案例。

答案 8 :(得分:0)

对我来说,以下方法是最简洁,最容易阅读的方法

from subprocess import Popen, PIPE

def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
    with open(output_filename, 'wb') as out_f:
        p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
        p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
        p1.communicate(input=bytes(input_s))
        p1.wait()
        p2.stdin.close()
        p2.wait()

可以这样称呼:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')