批量给一堆机器分发公钥、拷文件,脚本走到要输密码或 yes/no 的地方就会整个卡住。expect 就是解决这个问题的:它模拟用户输入、读取程序的输出,匹配到提示就自动应答。最早是为 Telnet、SSH、FTP 这类网络服务的自动化交互设计的,任何需要模拟人工敲键盘的场合都能用。
能力与边界
- 自动化交互:写脚本模拟用户行为,输入文本、等待特定输出、匹配文本、从输出里提取信息。
- 处理交互式程序:对需要人工确认的命令行程序(Telnet、SSH、FTP 等)自动登录、执行命令、处理输出。
- 条件匹配:根据输出里的文本模式决定下一步动作,一个脚本能处理多种情况。
- 脚本语言:本身支持流程控制、变量、函数,可读性和可维护性都在。
- 模块化:除了命令行,还有
expect模块可以嵌进 Tcl、Perl、Python,集成到更大的自动化任务里。 - 跨平台:各种 Unix/Linux 系统都有,也有若干变体和替代工具。
写脚本的思路一致:把预期的交互步骤排进去,等待输出、按输出决定下一步。不过有些场景下更现代的工具更合适,比如 Python 的 pexpect 库。
安装
expect 依赖 tcl,先确认装没装:
[root@localhost ~]# whereis tcl
tcl: /usr/lib64/tcl8.5 /usr/share/tcl8.5
没有就先补 tcl 再装 expect:
[root@localhost ~]# yum install -y tcl
[root@localhost ~]# yum install -y expect
确认安装路径:
[root@localhost ~]# command -v expect
/usr/bin/expect
常用命令速查
spawn <command>:启动新的交互式进程,后面跟要执行的命令。expect <pattern> {action}:等输出里匹配到 pattern 就执行 action,可以多次使用匹配不同模式。send <string>:向当前进程发送字符串。send -- "<string>":发送含特殊字符的字符串,防止被解释。exp_send <string>:功能同send。exp_continue:在 expect 块里继续匹配后续模式,而不是结束执行。send_user <string>:打印信息,相当于脚本里的 echo。interact:把控制权交还给用户,允许手动交互。exit:退出 expect 脚本。eof:表示脚本执行结束。set <variable> <value>:定义变量并赋值。puts <string>:输出字符串。set timeout <seconds>:设超时时间,超时未匹配就走超时处理。
实战一:免交互拷贝公钥
给新机器批量铺 SSH 公钥,脚本长这样:
[root@localhost ~]# vim autocopy.exp
#!/usr/bin/expect
# 设置超时时间为10秒
set timeout 10
# 从命令行参数获取远程主机名和密码
set user_hostname [lindex $argv 0]
set password [lindex $argv 1]
# 启动 ssh-copy-id 命令,并将远程主机名传递给它
spawn ssh-copy-id $user_hostname
# 使用 expect 进行模式匹配和交互操作
expect {
"(yes/no)?" {
# 如果遇到 "(yes/no)?" 提示,自动发送 "yes" 并等待密码提示
send "yesn"
expect "*assword:" {
# 发送密码并等待完成
send "$passwordn"
}
}
"*assword:" {
# 如果直接遇到密码提示,发送密码并等待完成
send "$passwordn"
}
}
# 等待并处理脚本执行结束
expect eof
[root@localhost ~]# chmod +x autocopy.exp
流程拆解:超时设 10 秒;从命令行参数拿远程主机名和密码;spawn ssh-copy-id;expect 块分别处理首次连接的 “(yes/no)?” 提示(先回 yes 再等密码提示)和直接的 “*assword:” 提示;最后 expect eof 等执行结束。
调用方式:
[root@localhost ~]# ./autocopy.exp root@192.168.66.100 密码
执行后公钥直接写入对端,全程没有人工输入:

实战二:scp 自动应答
同样的骨架换成交互 scp,多传源文件、目标文件两个参数,并等 “100%” 出现确认拷贝完成:
[root@localhost ~]# vim autocopy.exp
#!/usr/bin/expect
# 设置超时时间为10秒
set timeout 10
# 从命令行参数获取远程主机名、源文件、目标文件和密码
set user_hostname [lindex $argv 0]
set src_file [lindex $argv 1]
set dest_file [lindex $argv 2]
set password [lindex $argv 3]
# 启动 scp 命令,将源文件复制到远程主机的目标位置
spawn scp $src_file $user_hostname:$dest_file
# 使用 expect 进行模式匹配和交互操作
expect {
"(yes/no)?" {
# 如果遇到 "(yes/no)?" 提示,自动发送 "yes" 并等待密码提示
send "yesn"
expect "*assword:" {
# 发送密码并等待完成
send "$passwordn"
}
}
"*assword:" {
# 如果直接遇到密码提示,发送密码并等待完成
send "$passwordn"
}
}
# 等待复制完成的 "100%" 提示
expect "100%"
# 等待并处理脚本执行结束
expect eof
[root@localhost ~]# chmod +x autocopy.exp
用法:autoscp.exp [user@]hostname 源文件 目标目录 [password]。这个脚本对已经做过公钥认证的机器也能跑,因为公钥认证默认优先于密码认证,password 项是可选的;反过来在没做公钥认证的机器上 password 是必选,不然免交互就没有意义了。
注意事项
密码走命令行参数明文传递,在机器上用 ps 或 shell history 都能看到,批量脚本里更稳妥的做法是把密码放进受限权限的文件再读取,或者干脆先铺公钥、后续全用密钥。两个脚本记得 chmod +x。timeout 按目标机器的响应速度调,网络差的环境 10 秒可能不够,匹配不到提示脚本会提前退出。


















暂无评论内容