原文:http://www.fblinux.com/?p=526
Expect介绍
expect是一个用来实现自动交互功能的软件套件,是用来实现自动和交互式任务程序进行通信,无需人的手工干预。比如SSH、FTP等,这些程序正常情况下都需要手工和他们交互,而使用expect就可以模拟人手工交互的过程,实现自动化运维的目的。
总结一句话就是:expect就是为系统管理的自动交互类需求而产生的。
本文将介绍expect的基本语法,最后会有两个典型的交互式生产实例,分别是:
1、 ssh文件批量分发
2、 openvpn帐号自动创建
环境介绍
Expect服务器:IP:192.168.10.2 系统:Centos 6.8 64位
Client 服务器:IP:192.168.10.5 系统:Centos 6.8 64位
Expect安装
1
|
yum -y
install
expect
|
Expect简单应用
在我们ssh连接服务器的情况下,如果没有把自己的公钥复制对目标主机的.ssh/authorized_keys文件下,我们是需要输入密码才可以连接的,但是在ssh脚本中如何实现自己输入密码?这个问题我们使用expect就可以解决,看如下操作:
直接使用ssh连接主机需要输入密码:
1
2
3
4
5
6
7
8
9
10
|
[root@ansible ~]
# ssh 192.168.10.5 /sbin/ifconfig eth0
root@192.168.10.5's password:
eth0 Link encap:Ethernet HWaddr 00:16:3E:03:78:60
inet addr:192.168.10.5 Bcast:192.168.10.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:273007 errors:0 dropped:0 overruns:0 frame:0
TX packets:374106 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:93143657 (88.8 MiB) TX bytes:91464671 (87.2 MiB)
Interrupt:160
|
这是一个写好的expect脚本
1
2
3
4
5
6
7
8
9
10
11
12
|
[root@ansible ~]
# cat expect.expect
#!/usr/bin/expect
spawn
ssh
192.168.10.5
/sbin/ifconfig
eth0
set
timeout 60
expect {
-timeout 5
"yes/no"
{ exp_send
"yes\r"
}
"*password:"
{ exp_send
"passtest\r"
}
timeout {puts
"expect was timeout by fblinux."
;
return
}
}
expect eof
exit
|
使用expect命令执行这个脚本,我们可以看到没有提示我们输入密码就可以在目标主机执行命令
1
2
3
4
5
6
7
8
9
10
11
|
[root@ansible ~]
# expect expect.expect
spawn
ssh
192.168.10.5
/sbin/ifconfig
eth0
root@192.168.10.5's password:
eth0 Link encap:Ethernet HWaddr 00:16:3E:03:78:60
inet addr:192.168.10.5 Bcast:192.168.10.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:273073 errors:0 dropped:0 overruns:0 frame:0
TX packets:374223 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:93162147 (88.8 MiB) TX bytes:91491805 (87.2 MiB)
Interrupt:160
|
至于为什么?在看完下面的expect语法介绍后,你就明白了
Expect语法
Spawn
Spawn命令是expect的初始命令,他用于启动一个进程,之后所有expect都在这个进程中进行,如果没有spawn语句,整个expect就没有办法进行了,spawn使用方法如下:
1
|
spawn
ssh
192.168.10.5
/sbin/ifconfig
eth0
|
在spawn命令后面,直接加上要启动的进程、命令信息,除此之外,spawn还支持其他选项如:-open:启动文件进程
-ignore:忽略某些信号
Expect
Expect表达式,动作 表达式 动作……
Expect命令用于等候一个相匹配内容的输出,一旦匹配上就执行expect后面的动作或命令,这个命令接收几个特有的参数,用的最多的就是-re,表示使用正则表达式的方式匹配,使用案例如下
1
2
|
spawn
ssh
-p22 root@192.168.100.2
/sbin/ifconfig
expect
"*password:"
{ send
"passtest\r"
}
|
从上面的例子可以看出,expect是依附与spawn命令的,当执行ssh命令后,expect就匹配命令执行后的关键字:password:,如果匹配到了关键字就执行包含在{}括号中的send或exp_send动作,匹配以及动作可以放在二行,这样就不需要使用{}括号了,就像下面这样,实际完成的功能与上面是一样的。
1
2
3
|
spawn
ssh
-p22 root@192.168.100.2
/sbin/ifconfig
expect
"*password:"
send
"passtest\r"
|
exp_send和send
在上面的介绍中,我们已经看到了exp_send命令的使用,exp_send是expect中的动作命令,可以发送一些特殊符号,\r表示回车
,\n换行
、\t制表符
等等,这些都与TCP中的特殊符号相同。
1
2
3
|
spawn
ssh
-p22 root@192.168.100.2
/sbin/ifconfig
expect
"*password:"
send
"passtest\n"
|
send命令还有几个可用的参数:-i:指定spawn_id,这个参数用来向不同的spawn_id的进程发送命令,是进行多程序控制的关键参数。
-s:s代表slowly,也就是控制发送的速度,这个参数使用的时候要与expect中的变量send_slow相关联。
Exp_continue
这个命令一般用在动作中,它被使用的条件比较,看看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
|
#!/usr/bin/expect
spawn
ssh
192.168.10.5
/sbin/ifconfig
eth0
set
timeout 60
expect {
-timeout 5
"yes/no"
{ exp_send
"yes\r"
;exp_continue }
"*password:"
{ exp_send
"passtest\r"
}
timeout {puts
"expect was timeout by fblinux."
;
return
}
}
expect eof
exit
|
在这个例子中,可以发现exp_continue命令的使用方法,首先它要处于一个expect命令中,然后它属于一种动作命令,完成的工作就是从头开始遍历,也就是说如果没有这个命令,匹配第一个关键字以后就会继续匹配第二个关键字,但有了这个命令后,匹配第一个关键字以后,第二次匹配仍然从第一个关键字开始。
Send_user
Send_user命令用来把后面的参数输出到标准输出中去,默认的send、exp_send命令都是将参数输出到程序中去:
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#!/usr/bin/expect
if
{ $argc != 3 } {
#判断:如果执行脚本传入的参数少于3个,则在终端输入下面send_user中的内容
send_user
"usage: expect scp-expect.exp file host dir\n"
exit
}
#define var
set
file
[lindex $argv 0]
set
host [lindex $argv 1]
set
dir
[lindex $argv 2]
set
password
"passtest"
spawn
scp
$
file
root@$host:$
dir
expect {
"yes/no"
{send
"yes\r"
;exp_continue}
"*password"
{send
"$password\r"
}
}
expect eof
exit
|
如果执行脚本的时候参数少于三个,输入如下内容
1
2
|
[root@ansible ~]
# expect expect.expect
usage: expect
scp
-expect.exp
file
host
dir
|
如果执行脚本的时候传入正确参数则执行结果如下
1
2
3
4
|
[root@ansible ~]
# expect expect.expect /etc/passwd 192.168.10.5 /root/
spawn
scp
/etc/passwd
root@192.168.10.5:
/root/
root@192.168.10.5's password:
passwd
100% 1321 1.3KB
/s
00:00
|
exit
exit命令功能很简单,就是直接退出脚本,但是你可以利用这个命令对脚本做一些扫尾工作,比如下面这样:
1
2
3
4
|
exit
-onexit {
exec
rm
$tmpfile
#删除临时文件
send_user
"Oldboy say good bye to you!\n"
#终端输出字符
}
|
Expect变量
Expect中有很多有用的变量,他们的使用方法如下,比如:
- set 变量名 变量值 #设置变量
- puts $变量名 #读取变量
1
2
3
4
|
set
file
[lindex $argv 0]
set
host [lindex $argv 1]
set
dir
[lindex $argv 2]
set
password
"123456"
|
Timeout
Timeout是expect中的一个重要变量,他是一个全局性的时间控制开关,你可以通过为这个变量赋值来规定整个expect的操作的时间,注意这个变量是全局的,他不会纠缠于某一条命令,即使命令没有任何错误,到时间任然会激活这个变量,但这个时间到达以后除了激活一个开关之外不会做其他的事情,如何处理是脚本编写人员的事情。
使用案例如下:
1
2
3
4
|
set
timeout 60
spawn
ssh
root@192.168.10.5
expect
"password:"
{send
"word\r"
}
expect timeout {puts
"expect was timeout"
;
return
}
|
上面的处理中,首先将timeout变量设置为60秒,当出现问题的时候程序可能会停止下来,只要到60秒,就会激活下面的timeout动作。
在另一种expect格式中,我们还有一种设置timeout变量的方法,看看下面的例子。
1
2
3
4
5
6
7
|
spawn
ssh
root@192.168.10.5
expect {
-timeout 5
"yes/no"
{ exp_send
"yes\r"
}
"*password:"
{ exp_send
"redhat\r"
}
timeout {puts
"expect was timeout by fblinux."
;
return
}
}
|
生产场景expect使用案例
ssh分发
场景说明:比如你新安装了一批服务器,你需要通过ansible来管理服务器(ansible是通过ssh来认证的),这个时候如果每台服务器都使用ssh-copy-id来交互就比较麻烦了,因为每台服务器都需要输入密码,这种场景使用expect实现是极好的。
分发前的准备工作
1、 初始化服务器的初始密码必须是一样的,这个应该放在运维装机规范中
2、 把自己的公钥文件命名为authorized_keys放到一个目录中,因为脚本是直接拷贝.ssh目录
1
2
|
[root@ansible ~]
# mkdir /data/.ssh/
[root@ansible ~]
# cp .ssh/id_rsa.pub /data/.ssh/authorized_keys
|
准备完成之后就可以写脚本了,我写好的脚本如下:
expect脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
[root@ansible ~]
# cat /shell/expect.exp
#!/usr/bin/expect
if
{ $argc != 3 } {
send_user
"usage: expect scp-expect.exp file host dir\n"
exit
}
#define var
set
file
[lindex $argv 0]
set
host [lindex $argv 1]
set
dir
[lindex $argv 2]
spawn
scp
-P22 -r -p $
file
root@$host:$
dir
expect {
"yes/no"
{send
"yes\r"
;exp_continue}
"*password"
{send
"passtest\r"
}
}
expect eof
exit
|
shell脚本:
1
2
3
4
5
6
7
8
9
10
11
12
|
[root@ansible ~]
# cat /shell/ssh_init.ssh
#!/bin/bash
.
/etc/init
.d
/functions
host=
"192.168.10.5"
#多个主机使用空格分隔,也可以使用两个变量网段+IP的形式,然后循环IP地址
for
ip
in
$host;
do
expect
/shell/expect
.exp
/data/
.
ssh
/ $segment$ip
/root/
>>
/dev/null
if
[ $? -
eq
0 ];
then
action
"$segment$ip"
/bin/true
else
action
"$segment$ip"
/bin/false
fi
done
|
使用示例:
1
2
|
[root@ansible ~]
# sh /shell/ssh_init.ssh
192.168.10.5 [ OK ]
|
执行完成之后,我们就可以直接登录到目标主机,而无须输入密码
Openvpn用户自动创建脚本
场景说明:我们每次创建openvpn用户的时候都需要有一大堆交互如输入国家、省份、城市、组织、邮件等等信息,特别麻烦。我现在想写一个脚本,只需要输入员工姓名和邮箱,就会自动把生产的密钥信息,还有client安装包,以及使用教程发送给员工,让员工按照教程操作,我写的脚本如下:
注意:使用脚本需要配置邮件发送环境,不然创建成功后,无法自动发送证书文件和使用教程给员工。邮件发送参考我另外一篇博文Linux下使用mutt,msmtp发信
expect脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
[root@openvpn ~]
# cat /shell/vpn_expect.expect
#!/usr/bin/expect -f
if
$argc<1 {
puts stderr
"Usage: $argv0 need argv.\n"
exit
1
}
set
vpnuser [lindex $argv 0]
set
path
/usr/local/openvpn/easy-rsa/2
.0/
spawn $path
/build-key
$vpnuser
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"\r"
expect
"*"
send
"y\r"
expect
"*"
send
"y\r"
expect eof
exit
|
openvpn帐号开通shell脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
[root@openvpn ~]
# cat /shell/create_vpnuser.sh
#!/bin/bash
# 设置相关变量
work_dir=
/usr/local/openvpn/easy-rsa/2
.0
tmp_dir=
/data/tmp/openvpn
mail_content=
/shell/mail
.txt
client_config=client.ovpn
# 使用帮助
help(){
echo
'添加vpn用户执行命令sh create_vpnuser.sh add'
echo
'删除vpn用户执行命令sh create_vpnuser.sh del'
}
# 判断用户是否存在
add_user(){
# 交互输入用户名和邮箱
read
-p
"please input a user name:"
name
read
-p
"please input a user email:"
email
if
[ -f $work_dir
/keys/
$name.crt ];
then
echo
"新建vpn用户存在,请检查!"
exit
1
else
# 创建用户密钥
cd
$work_dir &&
source
.
/vars
/usr/bin/expect
/shell/vpn_expect
.expect $name
if
[ $? != 0 ];
then
echo
"创建密钥失败"
exit
10
fi
# 密钥和配置文件打包
cd
$work_dir
/keys/
cp
$name.* ca.crt $tmp_dir
if
[ $? != 0 ];
then
echo
"复制密钥失败"
exit
20
fi
sed
-i s@vpnclient@$name@g $tmp_dir/$client_config
cd
$tmp_dir
tar
zcf $name.
tar
.gz $name.* ca.crt $client_config openvpn-2.2.2-
install
.exe
# 发送邮件给员工
sed
-i s@vpnclient@$name@g $mail_content
cat
$mail_content | mutt -s
"VPN帐号开通"
$email -a $tmp_dir/$name.
tar
.gz
sed
-i s@$name@vpnclient@g $tmp_dir/$client_config
sed
-i s@$name@vpnclient@g $mail_content
fi
}
del_user(){
# 交互输入用户名
read
-p
"please input a user name:"
name
if
[ -f $work_dir
/keys/
$name.crt ];
then
cd
$work_dir &&
source
.
/vars
&& .
/revoke-full
$name
else
echo
"删除vpn用户不存在,请检查"
fi
}
main(){
case
$1
in
add)
add_user;
;;
del)
del_user;
;;
*)
help;
esac
}
main $1
|
发送邮件的内容:
1
2
3
4
|
[root@openvpn ~]
# cat /shell/mail.txt
hi vpnclient:
你的vpn帐号已经开通,使用方法见wiki连接;有问题及时与我联系。
这里放上你们公司wiki中的openvpn安装教程
|