Python调用含read交互的Bash脚本:实时输出与高效捕获优化
高效运行交互式Bash脚本的Python实现
核心思路
- 采用非阻塞批量读取子进程输出,减少IO调用次数提升效率
- 监控子进程状态,当检测到子进程阻塞等待输入时,立即flush已读取的所有输出,确保提示完整显示
- 读取用户输入后传递给子进程,继续执行
代码实现
import subprocess import fcntl import os import sys def run_interactive_script(script_path): # 启动子进程,绑定标准输入输出 proc = subprocess.Popen( ['bash', script_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # 合并stderr到stdout统一处理 text=True ) # 设置stdout为非阻塞模式 fd = proc.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) captured_output = [] buffer = [] while proc.poll() is None: try: # 批量读取数据块(默认4096字节,可根据需求调整) chunk = proc.stdout.read(4096) if chunk: buffer.append(chunk) sys.stdout.write(chunk) sys.stdout.flush() captured_output.append(chunk) else: # 无数据可读,判定子进程等待输入 if buffer: sys.stdout.flush() buffer = [] # 读取用户输入并传递给子进程 user_input = input() + '\n' proc.stdin.write(user_input) proc.stdin.flush() except BlockingIOError: # 非阻塞读取无数据,继续循环检测子进程状态 continue except Exception as e: print(f"Error: {e}", file=sys.stderr) break # 读取子进程剩余输出 remaining = proc.stdout.read() if remaining: sys.stdout.write(remaining) sys.stdout.flush() captured_output.append(remaining) return ''.join(captured_output), proc.returncode # 示例调用 if __name__ == "__main__": output, code = run_interactive_script("test.sh") print(f"\n脚本执行完成,返回码:{code}") print(f"捕获的全部输出:\n{output}")
关键细节说明
- 非阻塞读取:通过
fcntl修改子进程stdout的文件描述符属性,避免批量读取时阻塞等待数据 - 批量读取:每次读取4096字节的块,大幅减少IO操作次数,彻底解决逐字符读取的性能瓶颈
- 无依赖兼容:无需修改目标Bash脚本,也不依赖任何已知提示内容,适用于所有交互式Bash场景
- 实时输出保障:每次读取到数据块立即输出并flush,子进程等待输入前确保所有提示内容已显示
内容的提问来源于stack exchange,提问作者user1656308
相关产品推荐
相关产品推荐

