Paramiko invoke_shell使用send/recv读取命令输出卡住超时不结束问题
问题根因
paramiko的recv()方法默认是阻塞调用:当服务端没有新数据返回时,调用会无限等待,不会主动返回空值,所以你代码里if not tmp: break的判断永远不会触发,这是第6次迭代卡住的核心原因。- 你使用的是交互式shell(
invoke_shell创建的会话),命令执行完成后服务端不会主动关闭连接,所以不会发送EOF标记,只有连接断开时recv才会返回空字节。 - 现有逻辑仅在第一次接收数据前判断了
recv_ready(),后续循环直接调用recv(),没有做就绪判断,无数据时直接进入阻塞状态。 - 原有依赖超时异常退出的逻辑本身不符合设计规范,异常触发时机不稳定也会导致卡死。
修复方案
核心调整点
- 每次调用
recv()前先判断recv_ready(),连续多次无就绪数据就判定命令执行完成 - 对于网络设备交互式场景,通过匹配返回内容里的设备提示符来判断命令执行结束,可靠性最高
- 可以给会话设置超时时间,兜底避免永久阻塞
- 发送命令时补充换行符,确保设备识别命令结束(部分设备无换行不会执行命令)
修改后的代码片段
首先在创建交互式会话时设置超时兜底:
# SSHConnect方法中创建session的部分修改 session = ssh.invoke_shell() session.settimeout(10) # 10秒超时兜底,避免永久阻塞 return session
然后修改run_command中非Linux主机的执行分支:
# Command for switch of UFMAPL LOGGER.debug('Sending command to UFMAPL/Switch with send()') out = '' # 发送命令时加换行符,确保设备识别命令结束 self.shell.send(cmd + '\n') time.sleep(0.5) # 给设备预留初始处理时间,可根据实际调整 # 连续无数据计数阈值,超过就认为命令执行完成 no_data_counter = 0 max_no_data = 3 # 适配你设备的提示符正则,匹配>或#结尾的提示符 prompt_pattern = re.compile(r'[>#] ?$') counter = 1 print('\ncommand is : ' + cmd + '\n' ) while True: print('iteration number is : #' + str(counter)) if self.shell.recv_ready(): tmp = self.shell.recv(1024) counter = counter + 1 out += tmp.decode("utf-8") print('After iteration #' + str(counter) + ' out = ' + out + '\n\n') no_data_counter = 0 # 匹配到提示符直接退出,判断最准确 if prompt_pattern.search(out.strip()): break else: no_data_counter += 1 if no_data_counter >= max_no_data: break time.sleep(1) # 无数据时等待1秒再检查 # 原有ansi转义字符处理逻辑不变 ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') out = ansi_escape.sub('', out) print('Printing final value before return : ' + str(out +'\n')) return out
其他注意点
- 如果设备的提示符不固定,可以调整
prompt_pattern的正则规则适配你的场景 max_no_data和等待时间可以根据设备的响应速度调整,响应慢的设备可以适当调大- 不要去掉
settimeout的兜底设置,避免极端场景下的阻塞问题
内容的提问来源于stack exchange,提问作者Ariel Weiser
相关产品推荐
相关产品推荐

