如何使用subprocess.Popen通过管道连接多个进程?
我想知道怎么用Python的 subprocess
模块来执行下面这个命令。
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 个回答
要模拟一个 shell 管道:
from subprocess import check_call
check_call('echo "input data" | a | b > outfile.txt', shell=True)
而不调用 shell(可以参考 17.1.4.2. 替代 shell 管道):
#!/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")()
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)
当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。
你会对以下内容感到更满意。
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" )
把一部分工作交给命令行来处理。让它用管道连接两个进程。
如果把'script.awk'重写成Python,你会更开心,这样就不需要awk和管道了。
补充说明。以下是一些建议awk没有帮助的原因。
[有太多原因,无法通过评论来回应。]
awk增加了一个没有实际价值的步骤。awk处理的内容,Python都能做到。
对于大数据集,从awk到sort的管道可能会提高处理时间。但对于小数据集,这没有什么显著好处。你可以快速测量一下
awk >file ; sort file
和awk | sort
的效果,看看并行处理是否有帮助。对于sort来说,通常没有帮助,因为sort并不是一次性过滤。使用“Python来排序”的简单性(而不是“Python到awk再到排序”)避免了这里提出的具体问题。
虽然Python的代码比awk多,但它的表达更清晰,而awk有些隐含规则对新手来说不太明了,容易让非专业人士感到困惑。
awk(就像shell脚本本身)又增加了一种编程语言。如果所有这些都能用一种语言(Python)完成,去掉shell和awk就能减少两种编程语言,让人可以专注于任务中真正有价值的部分。
总之:awk并不能增加显著的价值。在这种情况下,awk反而增加了复杂性,导致需要提出这个问题。去掉awk会带来净收益。
附注 为什么构建管道(a | b
)这么难。
当命令行遇到 a | b
时,它需要做以下几步。
创建一个原始shell的子进程。这个子进程最终会变成b。
建立一个操作系统的管道。(不是Python的subprocess.PIPE)而是调用
os.pipe()
,它返回两个通过公共缓冲区连接的新文件描述符。此时,进程有来自父进程的stdin、stdout、stderr,以及一个将作为“a的stdout”和“b的stdin”的文件。再创建一个子进程。这个子进程用新的a的stdout替换自己的stdout,然后执行
a
进程。b的子进程用新的b的stdin替换自己的stdin,然后执行
b
进程。b的子进程等待a完成。
父进程在等待b完成。
我认为以上步骤可以递归使用来生成 a | b | c
,但你需要隐式地将长管道括起来,像是 a | (b | c)
。
由于Python有 os.pipe()
、os.exec()
和 os.fork()
,并且你可以替换 sys.stdin
和 sys.stdout
,所以可以用纯Python来实现上述操作。实际上,你可能还可以使用 os.pipe()
和 subprocess.Popen
找到一些捷径。
不过,把这个操作交给命令行来处理会更简单。