关于Python中Popen设置stdout/stdin为subprocess.PIPE的含义及疑问
搞懂subprocess.PIPE:从本质到你的示例分析
我当初第一次看subprocess文档的时候,也被这个PIPE的描述绕晕过,咱们一步一步拆解清楚:
1. subprocess.PIPE到底是什么?
当你给Popen的stdout/stdin/stderr设置为subprocess.PIPE时,Python会给当前子进程单独创建一个匿名管道。这个管道是单向的,专门用来在父进程(你的Python程序)和这个子进程之间传递数据:
- 设
stdout=PIPE:子进程的标准输出会传到这个管道里,你可以通过proc.stdout从父进程读取这些输出 - 设
stdin=PIPE:父进程可以通过proc.stdin往这个管道写数据,作为子进程的标准输入
2. 你关心的几个核心问题
问题1:两个子进程都设stdout=PIPE,输出会混淆吗?
完全不会!每个PIPE都是调用Popen时独立创建的,两个子进程的输出管道各自连到父进程,彼此之间没有任何关联。你需要分别通过proc1.stdout和proc2.stdout去读取它们的输出,不会出现混在一起的情况。
问题2:一个子进程stdout=PIPE,另一个stdin=PIPE,这俩管道是同一个吗?前者输出会自动变成后者输入吗?
绝对不是同一个,也不会自动连通!每个PIPE都是全新的独立管道,默认情况下这两个子进程的输入输出都只和父进程交互,互相之间没有通道。
如果想让A的输出直接作为B的输入,你需要主动把它们的管道连起来,代码应该这么写:
# 启动A,把它的stdout设为管道 proc_a = subprocess.Popen(["command_a"], stdout=subprocess.PIPE) # 启动B,把它的stdin直接绑定到A的stdout proc_b = subprocess.Popen(["command_b"], stdin=proc_a.stdout) # 必须关闭父进程中持有的proc_a.stdout,这样A在B读完输出后能正常退出 proc_a.stdout.close()
3. 你的示例代码分析
先看你写的这段代码:
import os import signal import subprocess import time # The os.setsid() is passed in the argument preexec_fn so # it's run after the fork() and before exec() to run the shell. pro = subprocess.Popen("sar -u 1 > mylog.log", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid) # Here another subprocess subprocess.Popen(some_command, stdin=subprocess.PIPE) time.sleep(10) os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
这里有两个关键问题:
- 你给
sar命令加了> mylog.log,这是让shell把sar的输出直接重定向到文件了——哪怕你设置了stdout=subprocess.PIPE,这个管道也完全没用,sar的输出根本不会传到Python这边。 - 就算去掉
> mylog.log,这两个子进程的管道也不会自动连通:第一个sar的输出管道连到Python,第二个some_command的输入管道也连到Python,它们之间没有直接的数据流。
如果想让sar的输出作为some_command的输入,应该修改成这样:
# 不用shell=True,直接传命令列表,避免重定向干扰 pro = subprocess.Popen(["sar", "-u", "1"], stdout=subprocess.PIPE, preexec_fn=os.setsid) # 把第二个进程的stdin绑定到sar的stdout subproc = subprocess.Popen(some_command, stdin=pro.stdout) # 关闭父进程中的pro.stdout,避免死锁 pro.stdout.close() time.sleep(10) os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
最后补充个小提醒:管道有缓冲区限制,如果父进程一直不读取子进程的stdout,子进程可能会因为缓冲区满而卡住,所以使用PIPE时要么及时读取,要么用proc.communicate()方法来处理,避免死锁。
内容的提问来源于stack exchange,提问作者KansaiRobot
相关产品推荐
相关产品推荐

