expect脚本常卡在密码提示无响应,主因是未精准匹配终端输出(如password:、password for user@host:等变体)或未处理ssh首次连接的yes/no确认;需用-re正则匹配、exp_continue续期、timeout防死锁,并优先采用ssh密钥免密方案。

Expect脚本为什么在Linux下常卡在密码提示却无响应
Expect不是简单“发字符串”,它依赖精准匹配终端输出。常见问题是脚本没等到password:(注意末尾冒号和空格)就急着发送密码,或者远程服务实际输出的是Password for user@host:、Enter passphrase for key:等变体。另外,SSH默认启用StrictHostKeyChecking,首次连接时会卡在The authenticity of host 'xxx' can't be verified.提示上,Expect若没显式处理这一行,就会永远挂起。
如何用spawn + expect + send安全完成密码交互
核心是三步闭环:启动进程 → 等待特定提示 → 发送对应响应。必须用expect的正则能力覆盖多种密码提示,并设置超时防死锁:
#!/usr/bin/expect -f
set timeout 10
set user "admin"
set host "192.168.1.100"
set pass "mypass123"
spawn ssh $user@$host
expect {
-re "yes/no.*" { send "yes\r"; exp_continue }
-re "password:?$" { send "$pass\r" }
timeout { exit 1 }
eof { exit 1 }
}
interact
-
exp_continue用于处理SSH首次连接确认,匹配后继续等待下一个提示 -
-re "password:?$"同时匹配password:和password(某些旧系统不带冒号) -
timeout分支必不可少,否则网络中断或服务异常时脚本会僵住 - 最后
interact把控制权交还给用户,适合调试;生产环境可替换为expect eof自动退出
比明文密码更安全的替代方案有哪些
硬编码密码在expect脚本里本质不安全——进程参数、ps列表、历史命令都可能泄露$pass。真正可行的降级方案只有两个:
- 用
ssh-keygen生成免密密钥对,配合ssh-copy-id部署公钥,彻底绕过密码交互 - 若必须输密码,改用
sshpass工具:sshpass -p "mypass" ssh admin@192.168.1.100,虽仍不完美但至少不依赖Expect解释器,且支持从文件读密码(sshpass -f /path/to/passfile)
Expect本身不提供密码加密或密钥管理能力,强行用base64或openssl enc混淆密码只是心理安慰——解密密钥必然要写在脚本里。
Expect脚本执行失败时最该检查的三处细节
90%的问题出在环境差异而非语法错误:
- 确认
which expect返回路径,且脚本首行#!/usr/bin/expect -f与之严格一致(有些系统装在/usr/local/bin/expect) - 运行时加
-d参数(expect -d script.exp)看详细匹配日志,重点观察expect: does \"xxx\" match regex...这类行 - 终端类型问题:
spawn默认不分配伪TTY,某些命令(如sudo)检测到非交互终端会直接拒绝密码输入,此时需加-o "TERM=ansi"或改用spawn -noecho ssh -t ...强制分配TTY
Expect的脆弱性恰恰在于它太贴近真实终端行为——你手动能敲出来的,Expect未必能“看到”或“触发”,所以永远优先考虑免交互方案,再谈自动化。











