Expect自动化交互
expect
是一种基于tcl语言的自动化工具,用于替代用户与程序之间的交互动作,实现自动化交互。它的应用场景包括 SSH、SCP、FTP、RSYNC 等任务中的免密交互,以及设置密码等自动化任务。
安装软件包方式:
rpm -q expect
rpm -q tcl
yum install -y expect
主要功能和语法
-
解释器环境:指定
expect
解释器路径使用
/usr/bin/expect
或/bin/expect
两个使用的是同一个inode,建议使用/usr/bin/expect
。示例:
#!/usr/bin/expect
-
开启进程追踪记录交互信息的功能:使用
spawn
命令spawn
后面通常跟一个 Linux 执行命令,表示开启一个会话、启动进程,并跟踪后续交互信息。 -
捕捉关键词(字符串):使用
expect
命令expect
判断上次输出结果中是否包含指定的字符串,如果有则立即返回,否则就等待超时时间后返回。它只能捕捉由
spawn
启动的进程的输出,用于接收命令执行后的输出,然后与期望的字符串匹配。 -
发送交互信息的功能:使用
send
命令send
向进程发送字符串,用于模拟用户的输入。该命令不能自动回车换行,通常需要加\r
(回车)或\n
(换行)。 -
结束
expect
免交互进程:使用expect eof
或interact
expect eof
:表示交互结束,等待执行结束,退回到原用户环境,与
spawn
对应。expecy
脚本默认等待10秒,当命令执行完后,默认停留10秒,自动切回原用户。(切换回原终端用户)
interact
:执行完成后保持交互状态,把控制权交给控制台,会停留在目标终端而不会退回到原终端,这时可以手工操作。
后的命令不起作用,比如
interact
后添加,并不会退出
root
用户。如果没有
interact
,登录完成后会退出,而不是留在远程终端。使用interact
会保持在当前终端而不会退回原终端,比如切换到root
用户后会一直在root
用户状态下。(留在当前终端用户)
注意:
expect eof
与interact
只能二选一。 -
设置超时时间:使用
set
命令expect
默认的超时时间是 10 秒,可以通过set
命令设置会话的超时时间。若不限制超时时间,将其设置为
-1
。示例:
set timeout 5
-
继续匹配判断项:使用
exp_continue
exp_continue
附加于某个expect
判断项之后,允许该项被匹配后,继续匹配该expect
判断语句中的其他项。它类似于控制语句中的
continue
语句,表示允许expect
继续向下执行指令。示例:以下脚本将判断交互输出中是否存在
yes/no
或*password
。如果匹配yes/no
则输出yes
并再次执行判断;如果匹配*password
则输出abc123
并结束该段expect
语句。expect { "(yes/no)" {send "yes\r"; exp_continue;} "*password" {set timeout 300; send "abc123\r";} }
注意:使用
exp_continue
时,如果跟踪的是像passwd
这样输入密码后就结束进程的命令,expect{}
外不要再加上expect eof
,因为spawn
进程结束后会默认向expect
发送eof
,这会导致后面的expect eof
执行报错。 -
输出回显信息:使用
send_user
send_user
用于向用户显示回显信息,相当于echo
。可以用来打印调试信息或提示信息。示例:
send_user "Connecting to server...\n"
-
接收传递的参数:使用
lindex
expect
脚本可以接收从 Bash 命令行传递的参数,使用[lindex $argv n]
获得,其中n
从 0 开始,分别表示第一个、第二个、第三个……参数。类似于位置变量,用法相同。示例:
set hostname [lindex $argv 0] ; # 相当于 hostname=$1 set password [lindex $argv 1] ; # 相当于 password=$2
注意:
expect
脚本不能通过bash
、source
或.
来执行(因为这三种方式是调用shell
解释器),只能通过绝对路径或相对路径来执行。
执行方式
-
为了让操作系统知道应该使用哪个解释器来运行
expect
脚本,需要明确指定expect
解释器来运行该脚本。例如:/usr/bin/expect myscript.exp
-
或者,脚本文件的第一行必须是 shebang(如
#!/usr/bin/expect
),并直接使用相对路径或绝对路径来执行脚本:./myscript.exp
拓展:常见的转义字符及其用法
- \n:表示换行符。
- \t:表示制表符。
- \r:表示回车符。
- \b:表示退格符。
嵌入执行模式
-
将
expect
过程嵌入到 Shell 脚本中,便于执行和处理。但对于ssh
、su
等会切换环境的交互式命令,通常不建议使用这种方式。创建用户并设置密码
#!/bin/bash # 获取用户和密码 user=$1 password=$2 # 添加用户(非交互命令放在 expect 外面) useradd $user # 开始免交互修改密码 /usr/bin/expect <<-EOF spawn passwd $user expect "新的*" send "${password}\r" expect "重新*" send "${password}\r" expect eof EOF
示例
-
免交SSH登录:
写一个免交互脚本,从a机器远程登录到b机器,并停留在B机器
#!/bin/expect spawn ssh root@20.0.0.12 expect "root@20.0.0.12's password:" send "123\r" expect eof
-
免交互修改用户密码:
# 先操作一遍更改密码,抓取回显字符 [root@localhost opt]# passwd dn 更改用户 dn 的密码 。 新的 密码: 无效的密码: 密码少于 8 个字符 重新输入新的 密码: passwd:所有的身份验证令牌已经成功更新。
#!/usr/bin/expect # 需要使用 expect 命令去执行脚本 # 设置超时时间 set timeout 5 # 开始一个跟踪进程,执行 passwd 命令 spawn passwd dn # 捕捉匹配字符串并发送相应输入,只要匹配到即可 expect "新的 密码" # 匹配输出中的“新的 密码” send "123abcd\\r" expect "重新输入新的 密码" send "123abcd\\r" # 结束交互 expect eof
-
免交互切换用户 (
su
命令):#!/usr/bin/expect # 设置超时时间 set timeout 5 # 从命令行参数获取用户名和密码 set username [lindex $argv 0] set password [lindex $argv 1] # 开始跟踪 su 命令 spawn su - $username # 匹配密码提示并发送密码 expect "密码" send "$password\r" # 打印确认信息 expect "*]#" send_user "ok\n" # 继续保持交互状态 interact
-
实现自动SSH登录:
可使用
expect
脚本自动登录 SSH,实现无密码交互。#!/usr/bin/expect #声明脚本使用的shell set ip 192.168.233.22 #设置变量ip的内容为 192.168.233.22 set user root #设置变量user的内容为root set password 123 #设置变量password的内容为123 set timeout 10 #设置超时时间为10s spawn ssh $user@$ip #捕捉ssh root@192.168.233.22,若捕捉到此内容则执行下面expect内容 expect { #spawn捕捉成功则继续使用expect捕捉 "yes/no" {send "yes\n";exp_continue} #若捕捉到yes/no此内容则输入yes\n,\n为回车,然后继续捕捉,若只捕捉一次则不需要继续捕捉exp_continue,可以不加这个 "password" {send "$password\n"} #继续捕捉内容password,捕捉到后输入变量$password的内容,\n为回车,不继续捕捉则不需要exp_continue } #interact expect eof #结束符,interact结束符会保留在ssh过去主机的环境中,可以进行操作, #若为expect eof结束符则在ssh过去的主机中等待10s且不能进行任何操作即返回自己主机环境
-
带参数的 SSH 登录:
可通过参数传递主机名和密码,实现更灵活的自动登录。
#!/usr/bin/expect # 设置超时时间 set timeout 5 # 从命令行参数获取主机名和密码 set hostname [lindex $argv 0] set password [lindex $argv 1] # 开始 SSH 登录 spawn ssh $hostname # 捕捉各种可能的情况 expect { "Connection refused" {send_user "SSH 访问被拒绝\n"} # 连接失败,如对方 SSH 服务关闭 "No route to host" {send_user "主机名/IP 有误\n"} # 找不到服务器,如输入的 IP 地址不正确 "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$password\r"} } # 交互结束符 interact exit