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

76 投票
9 回答
89800 浏览
提问于 2025-04-11 19:06

我想知道怎么用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 个回答

21

要模拟一个 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")()
34
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)

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

56

你会对以下内容感到更满意。

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没有帮助的原因。

[有太多原因,无法通过评论来回应。]

  1. awk增加了一个没有实际价值的步骤。awk处理的内容,Python都能做到。

  2. 对于大数据集,从awk到sort的管道可能会提高处理时间。但对于小数据集,这没有什么显著好处。你可以快速测量一下 awk >file ; sort fileawk | sort 的效果,看看并行处理是否有帮助。对于sort来说,通常没有帮助,因为sort并不是一次性过滤。

  3. 使用“Python来排序”的简单性(而不是“Python到awk再到排序”)避免了这里提出的具体问题。

  4. 虽然Python的代码比awk多,但它的表达更清晰,而awk有些隐含规则对新手来说不太明了,容易让非专业人士感到困惑。

  5. awk(就像shell脚本本身)又增加了一种编程语言。如果所有这些都能用一种语言(Python)完成,去掉shell和awk就能减少两种编程语言,让人可以专注于任务中真正有价值的部分。

总之:awk并不能增加显著的价值。在这种情况下,awk反而增加了复杂性,导致需要提出这个问题。去掉awk会带来净收益。

附注 为什么构建管道(a | b)这么难。

当命令行遇到 a | b 时,它需要做以下几步。

  1. 创建一个原始shell的子进程。这个子进程最终会变成b。

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

  3. 再创建一个子进程。这个子进程用新的a的stdout替换自己的stdout,然后执行 a 进程。

  4. b的子进程用新的b的stdin替换自己的stdin,然后执行 b 进程。

  5. b的子进程等待a完成。

  6. 父进程在等待b完成。

我认为以上步骤可以递归使用来生成 a | b | c,但你需要隐式地将长管道括起来,像是 a | (b | c)

由于Python有 os.pipe()os.exec()os.fork(),并且你可以替换 sys.stdinsys.stdout,所以可以用纯Python来实现上述操作。实际上,你可能还可以使用 os.pipe()subprocess.Popen 找到一些捷径。

不过,把这个操作交给命令行来处理会更简单。

撰写回答