本文主要讲解Shell脚本开发中Expect自动化交互式程序的应用实践

目录:

  1.Expect介绍

  2.安装Expect软件和简单实现交互功能

  3.Expect程序自动交互的重要命令及实践

  4.Expect程序变量介绍

  5.Expect程序中的if条件语句




一:Expect介绍

1)什么是Expect

  Expect是一个用来实现自动交互功能的软件套件,是基于TCL的脚本编程工具语言,方便学习,功能强大。


2)为什么要使用Expect

  在现今的企业运维中,自动化运维已经成为运维的主流趋势,但是在很多情况下,执行系统命令或程序时,系统会以交互式的形式要求运维人员输入指定的字符串,之后才能继续执行命令。例如,为用户设置密码时,一般情况下就需要手工输入2次密码。  

[root@aliyun shuzu]# passwd ming66
Changing password for user ming66.
New password: 
BAD PASSWORD: it is WAY too short
BAD PASSWORD: is too simple
Retype new password: 
passwd: all authentication tokens updated successfully.


  简单来说,Expect就是用来自动实现与交互式程序通信的,而无需管理员的手工干预。比如SSH、FTP远程连接等,正常情况下都需要手工与它们进行交互,而使用Expect就可以模拟手工交互的过程,实现自动与远端程序的交互,从而达到自动化运维的目的。

  以下是Expect的自动交互工作流程简单说明,依次执行如下操作:

  spawn启动指定进程——>expect获取期待的关键字——>send向指定进程发送指定字符——>进程执行完毕,退出结束




二:安装Expect软件和简单实现交互功能

准备好两台实验虚拟机,IP地址为192.168.1.7和192.168.1.8 并设置好yum源,安装Expect安装包

[root@aliyun ~]# yum install expect -y
[root@aliyun ~]# ifconfig |grep 'inet addr:'|awk -F "[ :]" '{print $13}'
192.168.1.7
127.0.0.1
[root@aliyun ~]# ssh -p22 root@192.168.1.8
The authenticity of host '192.168.1.8 (192.168.1.8)' can't be established.
RSA key fingerprint is c8:f2:9e:53:55:98:37:10:31:8f:1a:62:9a:58:c7:8e.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.8' (RSA) to the list of known hosts.
root@192.168.1.8's password: 
Last login: Sat Jun  9 03:31:56 2018 from 192.168.1.11
[root@aliyun ~]# ifconfig |grep 'inet addr:' |awk -F "[ :]" '{print $13}'
192.168.1.8
127.0.0.1
[root@aliyun ~]# exit
logout
Connection to 192.168.1.8 closed.
[root@aliyun ~]# ssh -p22 root@192.168.1.8
root@192.168.1.8's password: 
Last login: Sat Jun  9 03:37:29 2018 from 192.168.1.7


可以看到,每次都需要手工输入密码,才能执行ssh命令,否则无法执行

下面就用Expect的功能实现自动交互,发送密码并执行上诉ssh命令

[root@aliyun ~]# cat ywxi.exp 
#!/usr/bin/expect          #脚本开头解析器,和shell类似,表示程序使用expect解析
spawn ssh root@192.168.1.8 uptime   #执行ssh命令(注意开头必须要有spawn,否则无法实现交互)
expect "*password"          #利用expect获取执行上述ssh命令输出的字符串是否为期待的字符串*.password,这里的*是通配符
send "123456\n"            #当获取到期待的字符串*password时,则发送123456密码给系统,\n为换行
expect eof               #处理完毕后结束Expect


#执行expect脚本

[root@aliyun ~]# which expect
/usr/bin/expect
[root@aliyun ~]# expect ywxi.exp 
spawn ssh root@192.168.1.8 uptime
root@192.168.1.8's password: 
 03:46:18 up 16 min,  3 users,  load average: 0.00, 0.02, 0.06
[root@aliyun ~]# expect ywxi.exp 
spawn ssh root@192.168.1.8 uptime
root@192.168.1.8's password: 
 03:46:30 up 16 min,  3 users,  load average: 0.00, 0.02, 0.06




三:Expect程序自动交互的重要命令及实践

Expect程序中的命令是Expect的核心

1)spawn命令

  在Expect自动交互程序执行的过程中,spawn命令是一开始就需要使用的命令,通过spawn执行一个命令或程序,之后所有的Expect操作都会在这个执行过的命令或程序进程中进行,包括自动交互功能,因此如果没有spawn命令,Expect程序将无法实现自动交互。所以使用spawn命令是Expect程序实现自动交互工作流程中的第一步,也是最关键的一步

apawn命令的语法为:

spawn  [ 选项 ]   [ 需要自动交互的命令或程序 ]
选项:
-open 表示启动文件进程
-ignore 表示忽略某些信号

例如:

spawn ssh root@192.168.1.8 uptime


2)expect命令

  expect命令的作用就是获取spawn命令执行后的信息,看看是否和其事先指定的相匹配,一旦匹配上指定的内容就执行expect后面的动作,expect命令也有一些选项,相对用得较多的是-re,表示使用正则表达式的方式来匹配。

expect命令的语法为:

expect    表达式    [ 动作 ]

例如:

spawn ssh root@192.168.1.8 uptime
expect "*password"

注:上述命令不能直接在linux命令行中执行,需要放入expect脚本中执行


expect命令实践

范例1:执行ssh命令远程获取服务器负载值,并要求实现自动输入密码

法1:将expect和send放在一行
[root@aliyun ~]# cat ywxi.exp 
#!/usr/bin/expect
spawn ssh root@192.168.1.8 uptime 
expect "*password" {send "123456\n"}
expect eof
执行结果:
[root@aliyun ~]# expect ywxi.exp 
spawn ssh root@192.168.1.8 uptime
root@192.168.1.8's password: 
 04:06:30 up 36 min,  3 users,  load average: 0.00, 0.00, 0.00

法2:将expect和send不放在一行
[root@aliyun ~]# cat ywxi.exp 
#!/usr/bin/expect
spawn ssh root@192.168.1.8 uptime 
expect "*password" 
send "123456\n"
expect eof
执行结果:
[root@aliyun ~]# expect ywxi.exp 
spawn ssh root@192.168.1.8 uptime
root@192.168.1.8's password: 
 04:07:52 up 37 min,  3 users,  load average: 0.00, 0.00, 0.00


范例2:利用expect响应shell脚本中的多个read读入

脚本前准备:
[root@aliyun pratice]# cat readex.sh 
#!/bin/bash
read -p 'please input your username:' name
read -p 'please input your password:' pass
read -p 'please input your email:' email
echo -n "you name is $name,"
echo -n "you password is $pass,"
echo -n "you email is $email."
[root@aliyun pratice]# sh readex.sh 
please input your username:ywxi
please input your password:123456
please input your email:1031664140@qq.com
you name is ywxi,you password is 123456,you email is 1031664140@qq.com.

expect脚本如下:
[root@aliyun pratice]# cat expe.exp 
#/usr/bin/expect
spawn /bin/sh readex.sh                                #这里在一个目录下,用的相对路径
expect {
        "username" {exp_send "ywxi\r";exp_continue}    #若获取到的是username信息,则自动输入ywxi
        "*pass*"   {send "123456\r";exp_continue}      #若获取到的是*pass*信息,则自动输入123456
        "*mail*"   {exp_send "1031664140@qq.com\r"}    #若获取到的是*mail*信息,则自动输入邮件地址
}
expect eof
执行结果如下:
[root@aliyun pratice]# expect expe.exp 
spawn /bin/sh readex.sh
please input your username:ywxi
please input your password:123456
please input your email:1031664140@qq.com
you name is ywxi,you password is 123456,you email is 1031664140@qq.com.


3)send命令

  exp_send和send命令的使用方法,这两个命令是expect中的动作命令,用法类似,即在expect命令匹配指定的字符串后,发送指定的字符串给系统,这些命令可以支持一些特殊转义符号,例如: \r表示回车、\n表示换行、\t表示制表符等。

send命令有几个可用的参数,具体如下:

  -i : 指定spawn_id,用来向不同的spawn_id进程发送命令,是进行多程序控制的参数

  -s: s代表slowly,即控制发送的速度,使用的时候要与expect中的变量send_slow相关联


4)exp_continue命令

  这个命令一般处于expect命令中,属于一种动作命令,一般用在匹配多次字符串的动作中,从命令的拼写就可以看出命令的作用,即让Expect程序继续匹配的意思。

  使用示例如下:

[root@aliyun pratice]# cat expe.exp 
#/usr/bin/expect
spawn /bin/sh readex.sh
expect {
        "username" {exp_send "ywxi\r";exp_continue}
        "*pass*"   {exp_send "123456\r";exp_continue}
        "*mail*"   {exp_send "1031664140@qq.com\r"}
}
expect eof

  

5)send_user命令

  send_user命令可用来打印Expect脚本信息,类似shell里的echo命令,而默认的send、exp_send命令都是将字符串输出到Expect程序中去。

示例如下:

[root@aliyun pratice]# cat send.exp 
#!/usr/bin/expect
send_user "I am ywxi.\n"        #\n表示换行
send_user "I am linuxer,\t"     #\t表示Tab键
[root@aliyun pratice]# expect send.exp 
I am ywxi.
I am linuxer,   [root@aliyun pratice]




四:Expect程序变量介绍

1)普通变量

Expect中的变量定义、使用方法与TCL语言中的变量基本相同

定义变量的基本语法如下:

set 变量名 变量值

示例如下:

set password “123456”

打印变量的基本语法如下:

puts $变量名

范例:定义及输出变量

[root@aliyun pratice]# cat bl.exp 
#!/usr/bin/expect
set password "123456"
puts $password
send_user "$password\n"    #send_user 也可以打印输出
[root@aliyun pratice]# expect bl.exp 
123456
123456


2)特殊参数变量

  在Expect里也有与shell脚本里的$0、$1、$#等类似的特殊参数变量,用于接收及控制Expect脚本传参。

  在Expect中$argv表示参数数组,可以使用 [ index $argv n ] 接收Expect脚本传参,n从0开始,分别表示第一个 [ index $argv 0 ]参数、第二个 [ index $argv 1 ] 参数、第三个[ index $argv 2 ] 参数。

  范例:定义及输出特殊参数变量

[root@aliyun pratice]# cat var.exp 
#!/usr/bin/expect
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"

执行结果如下:
[root@aliyun pratice]# expect var.exp ywxi 192.168.1.7 /etc
ywxi    192.168.1.7     /etc
ywxi    192.168.1.7     /etc

  

  Expect接收参数的方式和bash脚本的方式有些区别,bash是通过$0...$n这种方式来接收的,而Expect是通过set<变量名称>[lindex $argv <param index>]来接收的,例如set file [lindex $argv 0]

  除了基本的位置参数外,Expect也支持其他的特殊参数,例如:$argc表示传参的个数,$argv0表示脚本的名字。

  范例:针对Expect脚本传参的个数及脚本名参数的实践

[root@aliyun pratice]# cat var.exp 
#!/usr/bin/expect
set file [lindex $argv 0] 
set host [lindex $argv 1]
set dir [lindex $argv 2]
puts "$file\t$host\t$dir"
puts $argc
puts $argv0

执行结果如下:
[root@aliyun pratice]# expect var.exp ywxi.txt 192.168.1.7 /opt/
ywxi.txt        192.168.1.7     /opt/        #这是脚本后面的三个参数
3                                            #这是参数的总个数
var.exp                                      #这是脚本的名字

  



五:Expect程序中的if条件语句

Expect程序中if条件语句的基本语法为:

if    {条件表达式}    {
        指令
}

或

if    {条件表达式}    {
        指令
}    else    {
        指令
}
说明:if关键字后面要有空格,else关键字前后都要有空格,{条件表达式}大括号里面靠近大括号处可以没有空格,将指令括起来的起始大括号"{"前要有空格。


范例如下:使用if语句判断脚本传参的个数,如果不符合则给予提示。

[root@aliyun pratice]# cat ifexp.exp 
#!/usr/bin/expect
if  {  $argc != 3 } {
        send_user "usage: expect $argv0 file host dir\n"
        exit
}
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir  [lindex $argv 2]
puts "$file\t$host\t$dir"
[root@aliyun pratice]# expect ifexp.exp 
usage: expect ifexp.exp file host dir
[root@aliyun pratice]# expect ifexp.exp  1 2 3
1       2       3