1. Expect 自动交互式程序介绍及安装
1.1 Expect 介绍
Expect 是一个用来实现自动化交互功能的软件套件。
1.2 为什么使用 Expect
在现今的企业运维中,自动化运维已经成为运维的主流趋势,但是在很多情况下,执行系统命令或程序时,系统会以交互式的形式要求运维人员输入指定的字符串,之后才能继续执行命令。例如:
① 为用户设置密码时,一般情况下就需要手工输入两次密码
② 使用 SSH 远程连接服务时,第一次连接要和系统实现两次交互式输入:
简单地说,Expect 就是用来自动实现与交互式程序通信的,而无需管理员手工干预。比如 SSH 、FTP 远程连接等,正常情况下都需要手工与它们进行交互,而使用 Expect 就可以模拟手工交互的过程,实现自动与远端程序的交互,从而达到自动化运维的目的。
1.3 Expect 的自动交互工作流程简单说明
spawn 启动指定进程 --->expect 获取期待的关键字 ---> send 向指定进程发送指定字符 ---> 执行完退出
1.4 安装 Expect 软件
在管理机上,确保机器可以正常上网,并设置好 yum 安装源,然后执行 yum install expect -y 安装。
安装过程如下:
[[email protected] ~]# rpm -qa expect # 检查是否安装。(没有输出说明未安装)
[[email protected] ~]# yum install expect -y # 执行安装命令。
[[email protected] ~]# rpm -qa expect # 再次检查是否安装(安装成功会输出版本号)。
expect-5.44.1.15-5.el6_4.x86_64
[[email protected] ~]# which expect # 查看命令路径。
/usr/bin/expect
2.Expect 自动化交互式程序应用实践
2.1 小试牛刀:通过 Expect 自动交互功能查看远程服务器负载
准备两台服务器,IP 和主机名列表为:
IP 地址
主机名
192.168.136.142
nfs-server
192.168.136.141
lamp01
远程连接查看负载
[[email protected] ~]# ssh -p22 [email protected] uptime
[email protected]‘s password: # 此处提示需要手工输入密码。
22:28:32 up 1:51, 1 user, load average: 0.00, 0.00, 0.00
利用 expect 的功能实现自动交互的脚本
[[email protected] ~]# cat ex.exp
#!/usr/bin/expect # 脚本开头解释器。
spawn ssh [email protected] uptime
expect "*password"
send "111111\n"
expect eof # 处理完毕后结束 expect 。
[[email protected] ~]# expect ex.exp
spawn ssh [email protected] uptime
[email protected]‘s password:
22:34:38 up 1:57, 1 user, load average: 0.00, 0.00, 0.00
提示:此时不需输入密码就自动连接到远端机器执行 ssh 命令了。
2.2 Expect 程序自动交互的重要命令及示例
2.2.1 spawn 命令
在 Expect 自动交互程序执行的过程中,spawn 命令是一开始就需要使用的命令,通过 spawn 执行一个命令或程序,之后所有的 Expect 操作都会在这个执行过的命令或程序进程中进行,包括自动交互功能,因此如果没有 spawn 命令,Expect 程序将无法实现自动交互。
语法:spawn 【选项】 【需要自动交互的命令或程序】
例如:spawn ssh [email protected] uptime # (Expect 脚本中命令)
在 spawn 命令的后面,直接加上执行的命令或程序(例如 ssh 命令)等,除此之外,spawn 还支持如下选项:(不常用,了解)
-open 表示启动文件进程。
-ignore 表示忽略某些信号。
提示:使用 spawn 命令是程序实现自动化交互工作流程中的第一步,也是最关键的一步。
2.2.2 expect 命令
在 Expect 自动交互程序执行的过程中,当使用 spawn 命令执行一个命令或程序之后,会显示出某些交互式信息,expect 命令的作用就是获取 spawn 命令执行后的信息,看看是否和其事先制定的相匹配,一旦匹配上指定的内容就执行 expect 后面的动作,expect 命令也有一些选项,用的最多的一个就是:-re 表示使用正则表达式 的方式来匹配。
语法:expect 【表达式】 【动作】
例如:spawn ssh [email protected] uptime
expect "*password" {send "123456\r"} # (Expect 脚本中命令) 。
范例 1:执行 ssh 命令远程获取服务器负载值,并要求实现自动输入密码。
方法一:将 expect 和send 放在同一行:
[[email protected] ~]# cat ex01.exp
#!/usr/bin/expect # 脚本开头解释器。
spawn ssh [email protected] uptime
expect "*password" {send "111111\n"}
expect eof # 处理完毕后结束 expect 。
方法二:将 expect 和send 放在不同行:
[[email protected] ~]# cat ex02.exp
#!/usr/bin/expect # 脚本开头解释器。
spawn ssh [email protected] uptime
expect "*password"
send "111111\n"
expect eof # 处理完毕后结束 expect 。
上面两种方法的执行结果是一样的:
[[email protected] ~]# expect ex.exp
spawn ssh [email protected] uptime
[email protected]‘s password:
22:34:38 up 1:57, 1 user, load average: 0.00, 0.00, 0.00
提示:expect 命令还可以在一个 expect 匹配中多次匹配不同的字符串,并给出不同的处理动作。只需要将匹配的所有字符串放在一个 { } 即可(需要借助 exp_continue 实现继续匹配)。
范例二:执行 ssh 命令远程获取服务器负载值,并要求实现自动输入 yes 及用户密码。
实现的脚本:
[[email protected] ~]# cat ex03.exp
#!/usr/bin/expect
spawn ssh [email protected] uptime
expect { # 大括号前要有空格。
"yes/no" {exp_send "yes\r";exp_continue}
"*password" {exp_send "111111\r"}
}
expect eof # 处理完毕后结束 expect 。
执行结果:
① 首先清除密钥文件使其出现 yes/no 提示信息:
[[email protected] ~]# ll ~/.ssh/known_hosts
-rw-r--r-- 1 root root 1191 Dec 11 01:58 /root/.ssh/known_hosts
[[email protected] ~]# rm -f ~/.ssh/known_hosts
② 执行脚本:
[[email protected] ~]# expect ex03.exp
spawn ssh [email protected] uptime
The authenticity of host ‘192.168.136.141 (192.168.136.141)‘ can‘t be established.
RSA key fingerprint is 8a:f2:af:0c:86:03:5c:35:25:75:5d:75:1d:9f:3c:8b.
Are you sure you want to continue connecting (yes/no)? yes # expect 自动输入 yes 。
Warning: Permanently added ‘192.168.136.141‘ (RSA) to the list of known hosts.
[email protected]‘s password: # expect 自动输入密码。
23:06:05 up 2:29, 1 user, load average: 0.00, 0.00, 0.00
范例三:利用 expect 响应 Shell 脚本中的多个 read 读入:
① 准备数据:利用 read 提示用户输入,创造交互式输入。
[[email protected] ~]# cat expread.sh
#!/bin/sh
read -p ‘please input your username:‘ name
read -p ‘please input your password:‘ pass
read -p ‘please input your email:‘ mail
echo -n "your name is $name,"
echo -n "your password is $pass,"
echo -n "your email is $mail."
② 执行结果:(下面的提示输入只能手动输入)
[[email protected] ~]# sh expread.sh
please input your username:alinuxer
please input your password:111111
please input your email:[email protected]
your name is alinuxer,your password is 111111,your email is [email protected]
问题:开发 expect 自动化脚本,根据需求自动输入多个字符串:
[[email protected] ~]# cat ex04.exp
#!/usr/bin/expect
spawn /bin/sh expread.sh
expect {
"username" {exp_send "alinuxer\r";exp_continue}
"*pass" {exp_send "123456\r";exp_continue}
"*mail" {exp_send "[email protected]\r"}
}
expect eof # 处理完毕后结束 expect 。
执行结果如下: (回车后无需任何人工交互,直接输出结果)
[[email protected] ~]# expect ex04.exp
spawn /bin/sh expread.sh
please input your username:alinuxer
please input your password:123456
please input your email:[email protected]
your name is alinuxer,your password is 123456,your email is [email protected]
2.2.3 send 命令
在上面的案例中用到的 exp_send 和 send 命令是 Expect 中的动作命令,用法类似,即在 expect 命令匹配指定的字符串后,发送的指定的字符串给系统,这两个命令支持一些特殊转义符号如\r(回车)、\n (换行)、\t (制表符)等
参数:
- i 指定 spawn_id ,用来向不同的 spawn_id 进程发送命令,是进行多程序控制的参数。
- s s 代表 slowly ,即控制发送的速度使用的时候要与 expect 中的变量 send_slow 相关联。
2.2.4 exp_continue 命令
一般处于 expect 命令中,属于一种动作命令,一般使用在匹配多次字符串的动作中,即让 Expect 程序继续匹配的意思。
提示:如果需要一次匹配多个字符串,那么不同的匹配之间就要加上 exp_continue ,否则 expect 将不会自动输入指定的字符串。最后一个结尾就不需要加 exp_continue 了,因为已经匹配完成了。
2.2.5 send_user 命令
该命令可以用来打印 Expect 脚本信息,类似 Shell 里的 echo (-e 参数)命令,而默认的 send、exp_send 命令都是将字符串输出到 Expect 程序中去。
示例如下:
[[email protected] ~]# vi ex05.exp
#!/usr/bin/expect
send_user "I am alinuxer.\n"
send_user "I like playing basketball.\n"
send_user "My girlfriend is KeXin.\n"
执行结果如下:
[[email protected] ~]# expect ex05.exp
I am alinuxer.
I like playing basketball.
My girlfriend is KeXin.
2.2.6 exit 命令
该命令的功能类似于 Shell 中的 exit ,即直接退出 Expect 脚本,除了最基本的退出脚本功能外,还可以利用该命令对脚本做一些关闭前的清理和提示等工作。
示例如下:
[[email protected] ~]# cat ex06.exp
#!/usr/bin/expect
send_user "I am alinuxer.\n"
send_user "I like playing basketball.\n"
send_user "My girlfriend is KeXin.\n"
exit -onexit {send_user "I am so cool!\n"
}
执行结果如下:
[[email protected] ~]# expect ex06.exp
I am alinuxer.
I like playing basketball.
My girlfriend is KeXin.
I am so cool!
Expect 常用命令总结:见<< 老男孩 Shell 编程实战 P325 >> 【略】
3. Expect 程序普通变量
定义变量的基本语法: set 变量名 变量值
打印变量的基本语法: puts $变量名
3.1 定义变量及输出变量示例
[[email protected] ~]# cat ex07.exp
#!/usr/bin/expect
set password "123456"
puts $password
send_user "$password\n" # send_user 也可以打印输出变量。
执行结果如下:
[[email protected] ~]# expect ex07.exp
123456
123456
3.2 Expect 程序特殊参数变量
在 Expect 里也有与 Shell 脚本里的$0、$1、$# 等类似的特殊参数变量,用于接收及控制 Expect 脚本参数。
在 Expect 中 $argv 表示参数数组,可以使用 [lindex $argv n] 接收 Expect 脚本传参,n 从零开始,分别表示第一个参数([lindex $argv 0]),第二个参数([lindex $argv 1]),第三个参数([lindex $argv 2])......
范例 1:定义及输出特殊参数变量:
[[email protected] ~]# cat ex08.exp
#!/usr/bin/expect
#define var
set file [lindex $argv 0] # 定义特殊参数变量。
set host [lindex $argv 1] # 定义特殊参数变量。
set dir [lindex $argv 2] # 定义特殊参数变量。
send_user "$file\t$host\t$dir\n" # 打印特殊参数变量。
puts "$file\t$host\t$dir\n" # 打印特殊参数变量。
执行结果如下:(命令行传参)
[[email protected] ~]# expect ex08.exp a.log 192.168.136.142 /tmp
a.log 192.168.136.142 /tmp
a.log 192.168.136.142 /tmp
提示:注意 Expect 接受参数的方式和 bash 脚本的方式的区别。除了基本的位置参数外,Expect 也支持其他的特殊参数,例如:$argc 表示传参的个数,$argv0 表示脚本的名字。
范例 2:针对 Expect 脚本传参的个数及脚本名参数的实践:
脚本如下:
[[email protected] ~]# cat ex09.exp
#!/usr/bin/expect
#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir [lindex $argv 2]
send_user "$file\t$host\t$dir\n"
puts "$file\t$host\t$dir\n"
puts $argc
puts $argv0
执行的结果:
[[email protected] ~]# expect ex09.exp b.log 192.168.136.141 /opt
b.log 192.168.136.141 /opt
b.log 192.168.136.141 /opt
3 # 打印参数个数($argc)。
ex09.exp # 输出脚本名字($argv0)。
3.3 Expect 程序中的 if 条件句
范例 1 :使用 if 语句判断脚本传参的个数,如果不符合则给予提示:
脚本如下:
[[email protected] ~]# cat ex10.exp
#!/usr/bin/expect
if { $argc != 3 } {
send_user "Usage:expect $argv0 file host dir\n"
exit
}
#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir [lindex $argv 2]
puts "$file\t$host\t$dir"
执行的结果:
[[email protected] ~]# expect ex10.exp c.log 192.168.136.141 /home
c.log 192.168.136.141 /home
范例 2 :使用 if 语句判断脚本传参的个数,无论是否符合都给予提示:
脚本如下:
[[email protected] ~]# cat ex11.exp
#!/usr/bin/expect
if { $argc != 26 } {
puts "bad"
} else {
puts "good"
}
执行结果如下:
[[email protected] ~]# expect ex11.exp {a..z}
good
[[email protected] ~]# expect ex11.exp {a..y}
bad
3.4 Expect 中的关键字
Expect 中的特殊关键字用于匹配过程,代表某些特殊的含义或状态,一般只用于 Expect 命令中而不能在 Expect 命令外面单独使用。
① eof end-of-file 用于匹配结束符。
② timeout 控制时间的一个关键字变量。可通过为该变量赋值来规定整个 Expect 操作的时间。
4. 企业生产场景下的 Expect 案例
4.0 环境准备:准备三台服务器,IP 和主机名见下表
IP 地址
主机名
角色
192.168.136.142
nfs-server
管理机
192.168.136.141
lamp01
被管理机 1
192.168.136.143
lnmp02
被管理机 2
4.1 开发 Expect 脚本实现自动交互式批量执行命令
1)实现 Expect 自动交互的脚本:
[[email protected] ~]# cat zidongjiaohu.exp
#!/usr/bin/expect
if { $argc != 2 } {
puts "Usage:expect $argv0 ip command"
exit
}
#define var
set ip [lindex $argv 0]
set cmd [lindex $argv 1]
set password "111111"
#
spawn ssh [email protected]$ip $cmd
expect {
"yes/no" {send "yes\r";exp_continue}
"*password" {send "$password\r"}
}
expect eof
执行结果如下:
[[email protected] ~]# expect zidongjiaohu.exp 192.168.136.143 uptime
spawn ssh [email protected] uptime
[email protected]‘s password:
23:57:06 up 3:43, 1 user, load average: 0.00, 0.00, 0.00
[[email protected] ~]# expect zidongjiaohu.exp 192.168.136.141 "free -m"
spawn ssh [email protected] free -m
[email protected]‘s password:
total used free shared buffers cached
Mem: 980 177 802 0 26 45
-/+ buffers/cache: 105 874
Swap: 1023 0 1023
2)利用 Shell 循环执行 Expect 脚本命令:
[[email protected] ~]# cat sh_piliang_exp.sh
#!/bin/sh
if [ $# != 1 ]
then
echo $"Usage:$0 cmd"
exit 1
fi
cmd=$1
for n in 130 141 143
do
expect zidongjiaohu.exp 192.168.136.$n "$cmd"
done
执行结果如下:
[[email protected] ~]# sh sh_piliang_exp.sh uptime
spawn ssh [email protected] uptime
The authenticity of host ‘192.168.136.130 (192.168.136.130)‘ can‘t be established.
RSA key fingerprint is 8a:f2:af:0c:86:03:5c:35:25:75:5d:75:1d:9f:3c:8b.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ‘192.168.136.130‘ (RSA) to the list of known hosts.
[email protected]‘s password:
21:35:26 up 3:52, 1 user, load average: 0.00, 0.00, 0.00
spawn ssh [email protected] uptime
[email protected]‘s password:
22:40:24 up 3:52, 1 user, load average: 0.00, 0.00, 0.00
spawn ssh [email protected] uptime
[email protected]‘s password:
00:05:41 up 3:52, 1 user, load average: 0.00, 0.00, 0.00
4.2 开发 Expect 脚本实现自动交互式批量发送文件或目录
1)实现 Expect 自动化交互的脚本:
[[email protected] ~]# cat zidongjiaohu01.exp
#!/usr/bin/expect
if { $argc != 3 } {
puts "Usage:expect $argv0 file host dir"
exit
}
#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir [lindex $argv 2]
set password "111111"
spawn scp -p22 -rp $file [email protected]$host:$dir
expect {
"yes/no" {send "yes\r";exp_continue}
"*password" {send "$password\r"}
}
expect eof
执行结果如下:
[[email protected] ~]# expect zidongjiaohu01.exp
Usage:expect zidongjiaohu01.exp file host dir
[[email protected] ~]# expect zidongjiaohu01.exp /etc/hosts 192.168.136.130:/tmp
Usage:expect zidongjiaohu01.exp file host dir
[[email protected] ~]# expect zidongjiaohu01.exp /etc/hosts 192.168.136.130 /tmp
spawn scp -p22 -rp /etc/hosts [email protected]:/tmp
[email protected]‘s password:
hosts 100% 255 0.3KB/s 00:00
在 192.168.136.130 查看发送 hosts 文件结果是否成功:
[[email protected] tmp]# ll # 执行前没有 hosts 文件。
total 4
drwxr-xr-x 2 root root 4096 Dec 10 21:46 data
-rw-------. 1 root root 0 Dec 9 22:11 yum.log
[[email protected] tmp]# ll # 执行后有了 hosts 文件。
drwxr-xr-x 2 root root 4096 Dec 10 21:46 data
-rw-r--r-- 1 root root 255 Dec 11 01:53 hosts
-rw-------. 1 root root 0 Dec 9 22:11 yum.log
2)利用 Shell 循环执行 Expect 脚本命令:
[[email protected] ~]# cat sh_piliang_exp01.sh
#!/bin/sh
if [ $# -ne 2 ]
then
echo $"Usage:$0 file dir"
exit 1
fi
file=$1
dir=$2
for n in 130 141 143
do
expect zidongjiaohu01.exp $file 192.168.136.$n $dir
done
执行结果如下:表示批量发送文件成功。
[[email protected] ~]# sh sh_piliang_exp01.sh /etc/hosts /tmp
spawn scp -p22 -rp /etc/hosts [email protected]:/tmp
[email protected]‘s password:
hosts 100% 255 0.3KB/s 00:00
spawn scp -p22 -rp /etc/hosts [email protected]:/tmp
[email protected]‘s password:
hosts 100% 255 0.3KB/s 00:00
spawn scp -p22 -rp /etc/hosts [email protected]:/tmp
[email protected]‘s password:
hosts 100% 255 0.3KB/s 00:00
4.3 开发 Expect 脚本实现自动交互式批量执行 Shell 脚本
见<<老男孩 Shell 高级编程实战 P334~336>> 【略】
4.4 开发 Expect 脚本实现自动交互式批量分发公钥
1)本地生成密钥对:【一路回车】
[[email protected] ~]# ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/root/.ssh/id_dsa):
/root/.ssh/id_dsa already exists.
Overwrite (y/n)?
[[email protected] ~]# ll .ssh/
total 8
-rw------- 1 root root 668 Dec 11 01:55 id_dsa
-rw-r--r-- 1 root root 605 Dec 11 01:55 id_dsa.pub
2)开发 Expect 脚本自动化交互分发公钥到所有的服务器:
[[email protected] ~]# cat fenfa_pub.exp
#!/usr/bin/expect
#created by ZhangLei
if { $argc != 2 } {
send_user "Usage:expect $argv0 file host\n"
exit
}
#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set password "111111"
#start exec command
spawn ssh-copy-id -i $file "-p 22 [email protected]$host"
expect {
"yes/no" {send "yes\r";exp_continue}
"*password" {send "$password\r"}
}
expect eof
3)开发 Shell 脚本循环执行 Expect 脚本:
[[email protected] ~]# cat sh_xunhuanfenfa_pub.sh
#!/bin/sh
for n in 130 141 143
do
expect fenfa_pub.exp ~/.ssh/id_dsa.pub 192.168.136.$n
done
执行 Shell 脚本即可分发公钥到所有服务器,略。