subprocess.Popen与os.system对比:带交互的SSH命令问题
解决subprocess执行带sudo的ssh命令时的交互问题
嘿,我碰到过类似的坑,这就帮你搞定这个交互问题!首先得理清楚为啥os.system(foo)能正常工作,subprocess却不行:os.system会直接把当前终端的标准输入、输出、错误流绑定到子进程,所以ssh弹出的sudo密码请求能直接显示在你的终端里,你输入的密码也能正常传给远程服务器。但subprocess默认不会这么做——如果没正确配置IO流,子进程的输入输出会被管道接管,导致你看不到密码提示,输入也传不进去,自然就异常了。
下面给你两种靠谱的解决方案:
方案一:直接继承终端IO流(最简单)
如果你的脚本是在终端里直接运行的,只需要让subprocess完全继承父进程的IO流就行,不用搞复杂的管道,直接这么写:
import subprocess foo = "ssh -t -p 200 username@remoteserver -C -Y sudo <需要sudo权限的命令>" # 关键是让subprocess继承当前终端的stdin/stdout/stderr subprocess.run(foo, shell=True, stdin=None, stdout=None, stderr=None)
这里shell=True是必须的,因为你的命令里包含了空格、sudo参数这些shell需要解析的内容;stdin=None等参数会让subprocess直接用当前终端的IO流,和os.system的行为一致,这样就能正常看到密码提示并输入了。
方案二:用pty模块模拟终端(应对sudo的requiretty限制)
有些服务器的sudo配置里开启了requiretty选项(要求sudo必须在终端环境下运行),这时候即使你用了方案一,可能还是会报错。这时候就需要用pty模块创建一个伪终端,让ssh和sudo以为自己在真实终端里:
import os import pty import subprocess import sys def run_interactive_ssh(cmd): # 创建伪终端的主从两端 master_fd, slave_fd = pty.openpty() # 启动子进程,把IO绑定到伪终端的slave端 proc = subprocess.Popen( cmd, shell=True, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, close_fds=True ) # 关闭slave端,避免资源泄漏 os.close(slave_fd) try: # 循环读取伪终端的输出,打印到终端 while True: output = os.read(master_fd, 1024) if not output: break sys.stdout.write(output.decode()) sys.stdout.flush() # 读取用户输入,写入伪终端传给远程服务器 user_input = sys.stdin.readline() os.write(master_fd, user_input.encode()) except KeyboardInterrupt: # 处理Ctrl+C中断 proc.terminate() finally: os.close(master_fd) proc.wait() # 调用函数执行命令 foo = "ssh -t -p 200 username@remoteserver -C -Y sudo <需要sudo权限的命令>" run_interactive_ssh(foo)
这个方法会模拟一个终端环境,完美解决sudo对终端的要求,同时保证输入输出的正常交互。
额外提醒
- 如果你不想用
shell=True(避免shell注入风险),可以把命令拆分成列表形式,比如:
然后把foo = [ "ssh", "-t", "-p", "200", "username@remoteserver", "-C", "-Y", "sudo", "<你的命令>", "<命令参数>" ]shell=True去掉,直接传给subprocess就行。 - 不要随便用
capture_output=True或者stdout=subprocess.PIPE,这些会把输出捕获到内存里,导致你看不到密码提示,输入也传不进去。
内容的提问来源于stack exchange,提问作者Donald Hall
相关产品推荐
相关产品推荐

