Python编写Shell切换用户后如何捕获新Shell的stdout输出
问题原因
无法捕获su切换用户后输出的核心原因有两个:
subprocess.Popen搭配communicate()的逻辑是等待子进程完全终止后,才会一次性返回所有缓存的输出。而su root启动的是常驻的交互式Shell,只要用户不输入exit退出,进程就不会终止,communicate()会一直阻塞,自然拿不到中间执行命令的输出。- 代码中子进程的标准输出被绑定到了管道(
subprocess.PIPE),管道不属于真正的终端设备(tty)。su、交互式bash这类程序检测到输出端不是tty时,会默认开启大块输出缓冲、不打印命令提示符,甚至直接切换到非交互模式,进一步导致输出无法实时获取。
实现方案
要实现切换用户后实时捕获子Shell输出,需要用*伪终端(pty)*模拟真实终端环境,同时手动做输入输出的双向转发,不要用communicate()等待进程结束。Python标准库自带pty模块可直接实现该逻辑,不需要安装第三方依赖。
核心实现逻辑:
- 单独识别
su这类需要启动交互式会话的命令,走专门的交互分支,不套用普通单次命令的执行逻辑 - 调用
pty.openpty()创建主从配对的伪终端,将子进程的标准输入、输出、错误全部绑定到伪终端从端 - 主进程通过IO多路复用同时监听两个数据源:用户在自定义Shell的输入、子进程写入伪终端的输出
- 用户输入直接写入伪终端主端传给子Shell,子Shell返回的输出实时读取后打印,同时可以在主进程对这部分输出做任意存储、处理
- 检测到子进程退出(比如用户输入
exit)后,回收资源回到原自定义Shell的主循环
对应修改后的可运行代码:
import os import subprocess import pty import select import sys while True: command = input("$ ") cmd_parts = command.split() if not cmd_parts: continue # 原有cd命令逻辑 if "cd" in cmd_parts: target_path = cmd_parts[cmd_parts.index("cd") + 1] os.chdir(target_path) continue # 识别su这类交互式命令,走pty逻辑 if cmd_parts[0] == "su": master_fd, slave_fd = pty.openpty() # 启动子进程,所有IO绑定到伪终端从端 proc = subprocess.Popen( cmd_parts, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, close_fds=True ) os.close(slave_fd) sys.stdout.flush() while True: # 同时监听用户输入、子进程输出 r_list, _, _ = select.select([sys.stdin, master_fd], [], []) # 读取子进程输出 if master_fd in r_list: try: output = os.read(master_fd, 1024) if not output: break # 此处可添加自定义逻辑处理捕获到的输出 sys.stdout.write(output.decode()) sys.stdout.flush() except OSError: break # 读取用户输入传给子进程 if sys.stdin in r_list: input_data = sys.stdin.read(1) if not input_data: break os.write(master_fd, input_data.encode()) proc.wait() os.close(master_fd) continue # 普通非交互式命令走原有逻辑,补充stderr捕获 process = subprocess.Popen( cmd_parts, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) output, error = process.communicate() if output: print(output.strip("\n")) if error: print(error.strip("\n"))
补充说明
- 该方案完全模拟真实终端交互,
su的密码提示、切换用户后的命令提示符、命令输出都会实时显示,和直接在系统终端操作体验一致。 - 如果需要后台自动完成身份认证,不需要用户手动输入密码,可以在读取到子进程返回的密码提示字符串时,自动将对应密码写入伪终端主端即可,不需要弹出交互提示。
- 不要给交互式程序使用
subprocess.PIPE绑定标准IO,管道的非tty属性会导致绝大多数交互式程序出现输出缓冲、交互异常的问题。
内容的提问来源于stack exchange,提问作者JayJay
相关产品推荐
相关产品推荐

