如何让pexpect等待长耗时命令完全执行完成?
问题原因
核心问题是pexpect误匹配了命令的回显内容,而非命令执行完成后输出的目标内容:
- 用
sendline发送命令时,远程shell会先回显你输入的整条命令(比如你发的./run_5_secs.sh; echo 'COMPLETED'会被完整回显),这时候expect("COMPLETED")会直接匹配回显里的字符串,根本不等脚本执行完毕。 - 你先
expect("$")的逻辑也有误:这个$匹配的是ssh连接成功后的初始提示符,不是命令执行完成后出现的新提示符,所以会立即返回。
可靠解决方案
方案1:用独特自定义标记(最推荐)
通过使用足够独特的标记,避免和命令文本、脚本输出重复,确保pexpect只匹配命令执行完成后的输出。同时调整等待顺序:
修改你的极简示例代码:
import pexpect import time start_time = time.time() child = pexpect.spawn("ssh host") # 先等初始提示符出现,确认ssh连接就绪 child.expect(r"\$") # 用正则匹配提示符,处理特殊字符转义 # 用一个独一无二的标记,避免误匹配 unique_marker = "DOCKER_BUILD_FINISHED_9876" child.sendline(f"./run_5_secs.sh; echo '{unique_marker}'") # 先等标记出现,确认脚本执行完成 child.expect(unique_marker, timeout=1800) # 长命令设置足够超时时间 # 再等新提示符,确保shell回到就绪状态 child.expect(r"\$") end_time = time.time() elapsed_time = end_time - start_time print(f"Command executed in {elapsed_time:.2f} seconds")
关键要点:
- 先等待初始提示符,确保ssh连接稳定后再发命令。
- 标记要足够独特,比如加入随机字符串或特定前缀,避免和任何输出内容重复。
- 给长耗时命令设置足够大的
timeout(比如1800秒对应30分钟),防止超时中断。
方案2:等待命令执行后的提示符(无需自定义标记)
如果不想用标记,可以通过捕获初始提示符,然后等待命令执行后的相同提示符来判断命令完成。这种方法适合脚本输出中不会出现提示符内容的场景:
import pexpect import time start_time = time.time() child = pexpect.spawn("ssh host") # 捕获初始提示符,保存下来用于后续精确匹配 prompt_regex = r"(.*\$)" child.expect(prompt_regex) initial_prompt = child.match.group(1).decode() # 发送命令 child.sendline("./run_5_secs.sh") # 等待初始提示符再次出现,说明命令执行完成 child.expect_exact(initial_prompt, timeout=1800) end_time = time.time() elapsed_time = end_time - start_time print(f"Command executed in {elapsed_time:.2f} seconds")
关键要点:
- 用
expect_exact精确匹配提示符,避免正则误匹配脚本输出中的类似字符。 - 依赖提示符的稳定性,如果脚本执行中输出了和提示符一样的内容,会导致误判。
方案3:关闭命令回显(可选)
通过关闭远程shell的回显,避免pexpect匹配到命令本身的内容,但这种方法复杂度较高,仅作为备选:
import pexpect import time start_time = time.time() child = pexpect.spawn("ssh host") child.expect(r"\$") # 关闭回显 child.sendline("stty -echo") child.expect(r"\$") # 发送命令,此时不会回显命令内容 child.sendline("./run_5_secs.sh; echo 'COMPLETED'") child.expect("COMPLETED", timeout=1800) # 恢复回显 child.sendline("stty echo") child.expect(r"\$") end_time = time.time() elapsed_time = end_time - start_time print(f"Command executed in {elapsed_time:.2f} seconds")
注意:这种方法可能影响后续交互,需要谨慎使用。
调试技巧
如果还是出现匹配问题,可以开启日志记录所有交互内容,方便排查:
child.logfile = open("pexpect_interaction.log", "w")
查看日志就能清楚看到pexpect到底匹配了哪部分内容。
内容的提问来源于stack exchange,提问作者Ronny
相关产品推荐
相关产品推荐

