Expect脚本执行kill任务时的非确定性行为排查
问题分析:Expect脚本偶尔无法终止进程并卡住的原因
我尝试使用以下Expect代码片段,希望在指定时长后远程终止进程并导出日志,预期100%能终止进程并输出“Log dump complete”语句,但实际仅偶尔成功,有时会卡在无交互的root# Shell提示符,只能通过Ctrl+C退出,请问问题出在哪里?
spawn bash -c "ssh $username@$hostname" expect "Are you sure you want to continue connecting (yes/no)" { send "yes\r" } "Enter passphrase for key " { } "password:" { send "$password\r" } "#" { send "./some_script > ~/recording.log & sleep $DURATION; kill $!\r" expect "Terminated" { puts stdout "\nLog dump complete" flush stdout } }
核心问题点
$!的作用域与不可靠性$!是Bash的特殊变量,代表当前Shell会话最后一个后台进程的PID,但你在远程执行的命令中,sleep $DURATION是前台运行,虽然理论上$!还是some_script的PID,但远程Shell的输出顺序不可控,且如果some_script提前退出,$!会失效,导致kill命令执行无效,自然不会输出"Terminated"。Expect匹配逻辑存在竞态
脚本发送命令后只等待"Terminated",但实际场景中可能出现:- 远程Shell先返回
#提示符(比如kill执行后,Shell优先输出提示符,再输出"Terminated") some_script的输出干扰了"Terminated"的匹配
这两种情况都会导致Expect无限等待,最终卡住。
- 远程Shell先返回
缺少超时处理机制
脚本没有设置超时时间,一旦"Terminated"未出现,Expect会一直阻塞,无法自动恢复。
修复方案
方案1:调整命令与匹配逻辑(推荐)
修改远程执行命令,确保进程PID被稳定捕获,同时增加多分支匹配和超时处理:
set timeout 60 ;# 设置超时时间,避免无限等待 spawn bash -c "ssh $username@$hostname" expect { "Are you sure you want to continue connecting (yes/no)" { send "yes\r" exp_continue ;# 处理完认证后继续等待提示符 } "Enter passphrase for key " { exp_continue ;# 如需密钥密码,可在此补充send逻辑 } "password:" { send "$password\r" exp_continue } "#" { # 显式保存PID,用自定义标记替代系统输出,确保匹配可靠 send "./some_script > ~/recording.log & PID=\$!; sleep $DURATION; kill \$PID; wait \$PID 2>/dev/null; echo \"TASK_FINISHED\"\r" expect { "TASK_FINISHED" { puts stdout "\nLog dump complete" flush stdout } "#" { # 先出现提示符也判定为执行完成 puts stdout "\nLog dump complete" flush stdout } timeout { puts stdout "\nTimeout: 进程终止确认失败" flush stdout } } } }
关键修改说明
- 显式保存PID:用
PID=$!固定目标进程的PID,避免$!因后续命令失效 - 自定义终止标记:用
echo "TASK_FINISHED"生成稳定的匹配字符串,比依赖系统的"Terminated"更可靠 - 多分支匹配:同时匹配自定义标记和Shell提示符,覆盖所有正常执行场景
- 超时处理:设置超时时间,避免脚本无限阻塞
方案2:简化远程执行流程
如果不需要交互式Shell,直接通过ssh命令模式执行,减少Expect的交互处理:
set timeout 60 spawn ssh $username@$hostname "./some_script > ~/recording.log & PID=\$!; sleep $DURATION; kill \$PID; wait \$PID 2>/dev/null; echo TASK_FINISHED" expect { "Are you sure you want to continue connecting (yes/no)" { send "yes\r" exp_continue } "password:" { send "$password\r" exp_continue } "TASK_FINISHED" { puts stdout "\nLog dump complete" flush stdout } timeout { puts stdout "\nTimeout: 远程命令执行超时" flush stdout } }
内容的提问来源于stack exchange,提问作者Layman
相关产品推荐
相关产品推荐

