在管道中使用Python subprocess模块的stdout.close()

9 投票
1 回答
10760 浏览
提问于 2025-04-18 02:46

在Python的子进程模块中,如果我们想要在Python里运行一个命令行指令,

foo | grep bar

我们可能会使用

p1 = Popen(["foo"], stdout = PIPE)
p2 = Popen(["grep", "bar"], stdin = p1.stdout, stdout = PIPE)
p1.stdout.close()
output = p2.communicate()[0]

我对这一行p1.stdout.close()感到困惑。如果你能原谅我,我想通过我理解的程序流程来分析一下,希望能找出错误。

在我看来,当Python执行output = p2.communicate()[0]这一行时,Python会尝试调用p2,并意识到它需要从p1获取输出。所以它会调用p1,执行foo,然后把输出放到一个地方,以便p2可以继续执行。然后p2就完成了。

但是在这个分析过程中,p1.stdout.close()并没有实际发生。那么到底发生了什么呢?我觉得这些代码的顺序可能也很重要,所以下面的代码可能就不行:

p1 = Popen(["foo"], stdout = PIPE)
p1.stdout.close()
p2 = Popen(["grep", "bar"], stdin = p1.stdout, stdout = PIPE)
output = p2.communicate()[0]

这就是我目前的理解状态。

1 个回答

8

p1.stdout.close() 是让 foo 知道管道什么时候断开的关键,比如说 p2 提前退出的时候。

如果不调用 p1.stdout.close(),那么 p1.stdout 在父进程中会一直保持打开状态,即使 p2 退出了;p1 也不会知道没有人再读取 p1.stdout,所以 p1 会继续往 p1.stdout 写数据,直到操作系统的管道缓冲区满了,然后就会一直卡在那里。

如果想要模拟 foo | grep bar 这个命令,但不使用命令行的话,可以这样做:

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

with Popen(['grep', 'bar'], stdin=PIPE) as grep, \
     Popen(['foo'], stdout=grep.stdin):
    grep.communicate()

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

撰写回答