Python subprocess.Popen执行SSH遇密码请求无法终止问题
解决SSH指纹检查脚本因密码请求挂起的问题
最近我在写Python脚本处理工作站重映像后的SSH指纹问题:尝试通过SSH连接目标节点,收到指纹变更警告就处理;但如果没有错误,SSH会触发密码请求,这时候我想终止进程,结果脚本直接挂起了。更奇怪的是,在PyCharm里运行脚本完全正常,能顺利终止SSH并继续执行,但终端里运行就会卡壳。
先贴出我最开始的代码:
def ssh_fingerprint_changed(node): """ Checks if a node's ssh fingerprint has changed or an old key is found, which can occur when a node is reimaged. It does this by attempting to connect via ssh and inspecting stdout for an error message. :param node: the ip or hostname of the node :return: True if the node's fingerprint doesn't match the client's records. Else False. """ changed = False cmd = ["ssh", "-q", ADMIN_USER + "@" + node, "exit"] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) print("Checking for fingerprint changes") for line in proc.stdout: # loop on lines print("in for loop") # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD if b"Offending key" in line: print("Offending key found.") proc.stdin.write(b"no\n") # don't connect changed = True elif b"REMOTE HOST IDENTIFICATION HAS CHANGED!" in line: print("REMOTE HOST IDENTIFICATION HAS CHANGED!") changed = True print(changed) # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD if not changed: # then everything's good, but it will be waiting for a password to connect print("Good to go, terminating ssh test.") rc = proc.terminate() else: rc = proc.wait() return changed
问题根源拆解
折腾了半天,终于搞清楚两个核心问题:
- SSH的输出流向错了:不管是指纹变更的警告,还是密码请求的提示,SSH都是输出到
stderr而不是stdout!我之前只监听了stdout,所以完全捕获不到这些信息,当没有指纹问题时,SSH卡在等待密码输入的状态,导致循环一直阻塞,根本到不了后面的proc.terminate()代码。 - 终端和PyCharm的stdin差异:PyCharm运行脚本时,子进程的stdin不是交互式终端,SSH检测到无法进行交互式输入,直接就退出了;但终端里的stdin是交互式的,SSH会一直等用户输密码,自然就挂住了。
修复后的代码
针对这两个问题,我修改了脚本,核心是禁用SSH的交互模式,同时正确捕获stderr:
import subprocess import select # 替换成你的实际管理员用户名 ADMIN_USER = "admin" def ssh_fingerprint_changed(node): """ Checks if a node's ssh fingerprint has changed or an old key is found, which can occur when a node is reimaged. It does this by attempting to connect via ssh and inspecting output for an error message. :param node: the ip or hostname of the node :return: True if the node's fingerprint doesn't match the client's records. Else False. """ changed = False # 添加BatchMode=yes禁用密码交互,避免挂起;-q减少冗余输出 cmd = ["ssh", "-q", "-o", "BatchMode=yes", f"{ADMIN_USER}@{node}", "exit"] # 同时捕获stdout和stderr,确保拿到所有输出 proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True ) print("Checking for fingerprint changes") # 使用select监听两个流,避免单一流阻塞导致脚本挂起 active_streams = [proc.stdout, proc.stderr] while active_streams: # 等待可读的流 readable, _, _ = select.select(active_streams, [], []) for stream in readable: line = stream.readline() # 如果流已经读完,移除它 if not line: active_streams.remove(stream) continue # 检查指纹相关错误 if "Offending key" in line: print("Offending key found.") changed = True elif "REMOTE HOST IDENTIFICATION HAS CHANGED!" in line: print("REMOTE HOST IDENTIFICATION HAS CHANGED!") changed = True # 设置超时等待进程结束,防止意外阻塞 try: proc.wait(timeout=5) except subprocess.TimeoutExpired: proc.kill() print(f"Fingerprint change detected: {changed}") return changed
关键修改点说明
-o BatchMode=yes:强制SSH禁用所有交互式输入(包括密码请求),如果不能通过密钥认证,直接退出,彻底避免挂起等待输入的情况。- 捕获
stderr:终于能拿到SSH的所有错误信息了,指纹警告、认证失败的提示都在这里。 select模块监听流:避免因为某一个流没有输出而阻塞循环,确保能读取到所有输出内容。- 超时等待:给
proc.wait()加了5秒超时,就算有意外情况,也不会一直卡着。
现在不管在终端还是PyCharm里运行,脚本都能正常工作,不会再因为密码请求挂起了。
内容的提问来源于stack exchange,提问作者43Tesseracts
相关产品推荐
相关产品推荐

