使用pexpect.spawn批量SSH多服务器时遭遇pexpect.TIMEOUT错误求助
排查Python Pexpect SSH脚本的TIMEOUT错误
pexpect.TIMEOUT本质是脚本没在指定时间内匹配到预期输出(比如提示符、密码请求),以下是常见问题点和解决思路:
常见问题及修复方案
提示符匹配不精准
不同服务器的提示符差异很大,比如普通用户可能是user@server:~$,root用户是#,甚至有自定义提示符。如果脚本写死固定字符串,很容易匹配失败超时。- 先手动登录目标服务器,记录登录后、切换用户后的精确提示符。
- 改用正则表达式匹配覆盖常见场景,比如:
ssh.expect(re.compile(r'[$#] ')),匹配普通用户的$和root的#结尾的提示符。
未处理SSH首次连接的主机密钥提示
首次连接新服务器时,会出现Are you sure you want to continue connecting的主机密钥确认提示,脚本没处理的话会卡在这导致超时。- 扩展expect的匹配列表,捕获该提示并发送
yes:idx = ssh.expect(['Password:', 'Are you sure you want to continue connecting']) if idx == 1: ssh.sendline('yes') ssh.expect('Password:')
- 扩展expect的匹配列表,捕获该提示并发送
超时时间设置过短
网络延迟、服务器负载高都会导致响应变慢,默认超时时间可能不够。- 初始化spawn时增大timeout参数,比如:
ssh = pexpect.spawn('ssh user@host', timeout=30)(单位秒)。
- 初始化spawn时增大timeout参数,比如:
命令发送时机过早
比如登录后还没等提示符出现就发送切换用户命令,脚本会因为找不到后续匹配项超时。- 每一步操作前,必须确保已经匹配到上一步的结束提示符,再执行下一步命令。
切换用户的密码提示匹配错误
用sudo切换用户时,提示可能是[sudo] password for user:,而非普通的Password:,脚本没匹配到就会超时。- 确认切换命令的实际提示文本,添加到expect列表中。
示例修正脚本
假设你的原始脚本存在上述问题,以下是修正后的版本(包含异常排查输出):
import pexpect import re def ssh_exec(host, login_user, login_pass, target_user, target_pass, cmd): try: # 初始化SSH连接,设置超时时间 ssh = pexpect.spawn(f'ssh {login_user}@{host}', timeout=30) # 处理登录阶段的两种情况:密码提示或主机密钥确认 login_idx = ssh.expect(['Password:', 'Are you sure you want to continue connecting'], timeout=30) if login_idx == 1: ssh.sendline('yes') ssh.expect('Password:', timeout=30) ssh.sendline(login_pass) # 等待登录成功后的提示符 ssh.expect(re.compile(r'[$#] '), timeout=30) # 切换到目标用户 if target_user != login_user: ssh.sendline(f'su - {target_user}') switch_idx = ssh.expect(['Password:', re.compile(r'[$#] ')], timeout=30) if switch_idx == 0: ssh.sendline(target_pass) ssh.expect(re.compile(r'[$#] '), timeout=30) # 执行目标命令 ssh.sendline(cmd) ssh.expect(re.compile(r'[$#] '), timeout=30) # 提取并打印命令输出(过滤掉命令本身和提示符) output = ssh.before.decode().split(cmd)[-1].strip() print(f'[{host}] 执行结果:\n{output}\n') # 清理连接 ssh.sendline('exit') ssh.sendline('exit') ssh.close() except pexpect.TIMEOUT: # 超时后打印缓冲区内容,方便排查哪里出问题 print(f'[{host}] 超时错误!当前缓冲区内容:\n{ssh.before.decode()}\n') except pexpect.EOF: print(f'[{host}] 连接意外中断!当前缓冲区内容:\n{ssh.before.decode()}\n') # 读取hosts.txt中的服务器列表 with open('hosts.txt', 'r') as f: hosts = [line.strip() for line in f if line.strip()] # 批量执行示例 for host in hosts: ssh_exec(host, 'your_login_user', 'your_login_pass', 'target_user', 'target_pass', 'uptime')
排查技巧
遇到超时错误时,一定要打印ssh.before.decode(),它会显示超时前脚本捕获到的所有内容,通过这些内容就能明确脚本卡在了哪个阶段(比如是主机密钥确认没处理,还是提示符匹配错误)。
内容的提问来源于stack exchange,提问作者bprat3
相关产品推荐
相关产品推荐

