expect脚本卡在password提示处的根本原因是密码提示输出到stderr而expect默认只捕获stdout;需显式重定向stderr、用正则模糊匹配提示符、严格遵循spawn-send-expect顺序,并通过环境变量等安全传入密码。

expect 脚本为什么总是卡在 password 提示处
根本原因是 expect 默认只匹配标准输出(stdout),而很多命令(比如 ssh、sudo、ftp)把密码提示写到了标准错误(stderr)。不显式捕获 stderr,脚本就看不到 Password: 这行,自然一直等待。
实操建议:
- 所有
expect块前加exp_expect eof或用-o选项启动 expect(如expect -o),但更稳妥的是在 spawn 后立刻加log_user 0+expect {块中显式匹配eof和timeout - 对 ssh/sudo 类命令,spawn 时务必加
-f(强制伪终端)和重定向 stderr:例如spawn -noecho ssh -o StrictHostKeyChecking=no user@host 2>&1 - 密码提示字符串别硬写
"Password:"—— 不同系统/命令可能输出"password for"、"Enter password:"、"Password (for...",用正则模糊匹配更稳:expect -re "(?i)password.*"
spawn + send + expect 的顺序不能乱
常见错误是先 send 再 expect,或者漏掉 send "\r"(回车符)。expect 是同步阻塞模型:它必须看到预期输出后才执行下一条 send,否则挂起。
典型正确流程:
spawn ssh user@host-
expect -re "(?i)(yes/no|continue connecting)" { send "yes\r"; exp_continue } timeout { exit 1 }(处理首次连接确认) -
expect -re "(?i)password.*" { send "mypass\r" }(注意 \r 不可省) -
expect "$ "(等待 shell 提示符,避免命令没执行完就退出) send "ls -l\r"expect "$ "
漏掉任意一个 expect,后续 send 就不会触发;多写一个没对应的 expect,脚本会卡死。
密码明文写在脚本里太危险,怎么安全传入
直接在 expect 脚本里写 send "123456\r" 等同于把密码存在明文文件里,ps 可见、history 可查、日志可能泄露。
更安全的做法:
- 用环境变量传入:
export PASSWD="xxx"; expect script.exp,脚本内用set password $env(PASSWD),再send "$password\r" - 用
read从 stdin 读(适合交互式运行):stty -echo; read -s password; stty echo; export PASSWD=$password; expect script.exp - 绝对不要用
fileutil::cat或exec cat读密码文件 —— 文件权限稍有疏忽就全暴露
注意:expect 本身不加密,所有“安全”都依赖外部约束(环境变量生命周期、stdin 输入时机、文件权限 0400)。
expect 脚本调试时看不到交互过程
默认 log_user 1 会把远程输出原样打到终端,但 spawn 的输入/输出混合后容易混乱;设成 log_user 0 又完全黑盒,没法定位卡在哪。
推荐折中方案:
- 调试阶段开头加:
log_file /tmp/expect_debug.log; log_user 1 - 每条
send前加send_user ">>> sending: '$password'\n" - 每个
expect后加send_user " - 运行时加
-d参数:expect -d script.exp,会打印内部状态机跳转
真正上线前记得删掉 log_file 和 send_user —— 日志里会残留明文密码。


















