Python subprocess重定向疑问:如何实现用户与子进程自由交互?
嘿,我来帮你把这些子进程交互的问题理清楚,一步步解决你的需求,再解答你的疑问~
需求1:向子进程发送输入并获取输出
如果是一次性给子进程传输入、然后读取它的全部输出,用communicate()方法最稳妥,还能避免管道死锁的问题:
import subprocess # 启动子进程,创建输入输出管道 sub = subprocess.Popen( ['./binary'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True # 用文本模式,不用手动编码解码 ) # 发送输入,同时等待子进程结束并读取输出/错误 output, error = sub.communicate(input="你的输入内容\n") print("子进程输出:", output) if error: print("子进程错误信息:", error)
注意:communicate()是一次性操作,适合非交互式的场景,没法持续给子进程发输入。
需求2:重定向父进程的标准输入输出到子进程
其实这个很简单——只要让子进程直接继承父进程的标准输入、输出、错误流就行,不需要手动创建管道:
import subprocess import sys # 显式指定继承父进程的输入输出流 sub = subprocess.Popen( ['./binary'], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr ) sub.wait() # 等待子进程执行完毕
或者更省事:subprocess.Popen的stdin/stdout/stderr默认是None,本来就会继承父进程的对应流,所以直接写下面这句也能达到效果:
sub = subprocess.Popen(['./binary']) sub.wait()
这样子进程的输出会直接打印到父进程的终端,用户在父进程里的输入也会直接传给子进程。
需求3:实现用户与子进程的自由交互式交互
如果子进程是像bash、Python解释器这类需要终端交互的程序(要处理光标移动、换行、输入回显等),仅仅继承流可能不够——它们需要感知到自己在终端环境里。这时候用pty模块模拟终端是最佳方案:
import pty import subprocess # 一行代码搞定完全交互式体验 pty.spawn(['./binary'])
pty.spawn会自动处理终端的所有交互细节,用户操作起来就像直接启动子进程一样顺畅。如果需要更自定义的逻辑,也可以手动用pty.openpty()创建虚拟终端管道:
import pty import os import sys import subprocess # 创建虚拟终端的主从设备 master, slave = pty.openpty() # 让子进程连接到虚拟终端的从设备 sub = subprocess.Popen( ['./binary'], stdin=slave, stdout=slave, stderr=slave, text=True ) os.close(slave) # 关闭父进程端的从设备 # 把父进程的输入输出和虚拟终端主设备关联 try: while True: # 读取子进程输出并打印到父进程终端 data = os.read(master, 1024) if not data: break sys.stdout.write(data) sys.stdout.flush() # 读取用户输入并发送给子进程 input_data = sys.stdin.readline() os.write(master, input_data.encode()) except Exception: pass sub.wait() os.close(master)
你的疑问解答
1. 标准输入能否实现像输出那样的重定向?
当然可以!和标准输出的逻辑一样,只要让子进程继承父进程的标准输入就行:
- 启动子进程时不指定
stdin参数(默认继承); - 或者显式设置
stdin=subprocess.INHERIT(Python 3.3+); - 或者设置
stdin=sys.stdin。
这三种方式都能让用户的输入直接传递给子进程,和ls命令直接输出到父进程终端的逻辑一致。
2. 对已运行的进程是否可行?
很遗憾,不行。当你用subprocess.Popen启动子进程并指定了stdin=subprocess.PIPE后,子进程的标准输入已经和父进程创建的管道绑定死了。进程启动后,它的文件描述符是固定的,Python的subprocess模块没有提供修改已运行子进程输入输出重定向的方法。如果一定要改,只能借助gdb这类操作系统级工具,但这非常复杂,完全不推荐在Python代码里这么做。
内容的提问来源于stack exchange,提问作者ja2142

