Python subprocess.Popen与Linux read -p交互阻塞问题求解
解决Python与bash
read -p交互时的阻塞问题 问题根源
bash在非TTY(终端)环境下运行时,标准输出/错误默认采用块缓冲模式,而非行缓冲。read -p的提示信息会被暂存到缓冲区中,不会立即输出,导致Python端的readline()一直等待换行符,从而发生阻塞。部分环境中read -p的提示还可能输出到标准错误流(stderr),但缓冲问题依然会导致无法实时捕获。
解决方案
方案一:模拟TTY环境(推荐)
用Python的pty模块为子进程创建伪终端,让bash认为自己处于终端环境,此时bash会自动切换为行缓冲,read -p的提示会实时输出。
修改后的Python代码:
import subprocess import threading import re import pty import os class ExeCmd: def __init__(self, cmd, interactive_cmds=dict()): self.cmd = cmd self.err = bytearray() self.out = bytearray() self.ret: int # 创建伪终端主从设备 master_fd, slave_fd = pty.openpty() proc = subprocess.Popen( self.cmd, shell=False, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, close_fds=True ) os.close(slave_fd) # 关闭子进程侧的文件描述符 def process_stream(): while True: try: # 从伪终端读取数据 data = os.read(master_fd, 1024) if not data and proc.poll() is not None: break self.out.extend(data) line = data.decode() # 匹配交互提示并响应 for key, value in interactive_cmds.items(): if re.search(key, line): if str(value).startswith("Signals."): proc.send_signal(value) else: os.write(master_fd, str(value + '\n').encode()) break except OSError: break out_thread = threading.Thread(target=process_stream) out_thread.start() out_thread.join() proc.wait() self.ret = proc.returncode os.close(master_fd) if __name__ == '__main__': ExeCmd(['./s.sh'], {r'Enter input:': 'y'})
方案二:强制子进程无缓冲运行
通过stdbuf命令强制bash脚本的标准输入、输出、错误流使用无缓冲模式,让read -p的提示实时输出,无需修改原bash脚本。
修改调用代码的命令参数:
if __name__ == '__main__': # stdbuf参数:-i0输入无缓冲,-o0输出无缓冲,-e0错误流无缓冲 ExeCmd(['stdbuf', '-i0', '-o0', '-e0', './s.sh'], {r'Enter input:': 'y'})
方案三:修改bash脚本显式输出提示
如果允许修改bash脚本,可将read -p拆分为显式的echo和read,确保提示信息立即输出:
#! /bin/bash echo "Sample taking input from users" # 用echo -n输出无换行的提示,强制输出到标准输出 echo -n "Enter input:" # 手动触发缓冲区刷新 printf "" read
验证
运行修改后的代码,Python会实时捕获到Enter input:的提示,并自动输入y,脚本可正常执行无阻塞。
内容的提问来源于stack exchange,提问作者Equation Solver
相关产品推荐
相关产品推荐

