zoukankan      html  css  js  c++  java
  • 十六、Shell之expect自动化交互程

    一、什么是Expect

    Expect是一个用来实现自动交互功能的软件套件(Expect is a software suite for automating interactive tools,这是作者的定义),是基于TCL的脚本编程工具语言,方便学习,功能强大。

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

    [root@node1 ~]# passwd ywx
    Changing password for user ywx.
    New password: 
    BAD PASSWORD: The password is shorter than 8 characters
    Retype new password: 
    passwd: all authentication tokens updated successfully.
    [root@node1 ~]# 

    SSH远程第一次链接需要两次交互式输入

    [root@node1 ~]# ssh 192.168.32.213
    The authenticity of host '192.168.32.213 (192.168.32.213)' can't be established.
    ECDSA key fingerprint is SHA256:ObDtb5FnND2UfusUNwcwuhGZJnTHpUNRrIcruOi1p7c.
    ECDSA key fingerprint is MD5:cc:ea:8a:39:a9:c3:10:af:a8:73:01:bf:41:c6:07:48.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added '192.168.32.213' (ECDSA) to the list of known hosts.
    root@192.168.32.213's password: 
    Last login: Sun Sep 20 18:07:00 2020 from 192.168.32.102
    [root@node3 ~]# 

    简单地说,Expect就是用来自动实现与交互式程序通信的,而无需管理员的手工干预。

    以下是Expect的自动交互工作流程简单说明,依次执行如下操作: spawn启动指定进程→expect获取期待的关键字→send向指定进程发送指定字符→进程执行完毕,退出结束。

    二、Expect的安装

    [root@node1 ~]# rpm -qa expect
    [root@node1 ~]# yum install -y expect
    [root@node1 ~]# rpm -qa expect
    expect-5.45-14.el7_1.x86_64
    
    #expect常用命令:
    spawn               交互程序开始后面跟命令或者指定程序
    expect              获取匹配信息匹配成功则执行expect后面的程序动作
    send exp_send       用于发送指定的字符串信息
    exp_continue        在expect中多次匹配就需要用到
    send_user           用来打印输出 相当于shell中的echo
    exit                退出expect脚本
    eof                 expect执行结束 退出
    set                 定义变量
    puts                输出变量
    set timeout         设置超时时间

    三、简单实现expect

    实验环境
    node1: 192.168.32.211
    node2: 192.168.32.212
    
    1、在node1使用expect实现自动修改ywx用户的密码
    2、在node1上远程执行node2的uptime命令

    1、在node1使用expect实现自动修改ywx用户的密码

    #! /usr/bin/expect
    spawn passwd ywx
    expect {
    "New password:" { send "123456
    "; exp_continue }
    "Retype new password:" { send "123456
    " }
    }
    expect eof

    测试脚本

     
    [root@node1 scripts]# echo 654321|passwd --stdin ywx
    Changing password for user ywx.
    passwd: all authentication tokens updated successfully.  
    [root@node1 scripts]# expect expect3.exp 
    spawn passwd ywx
    Changing password for user ywx.
    New password: 
    BAD PASSWORD: The password is shorter than 8 characters
    Retype new password: 
    passwd: all authentication tokens updated successfully.
    [root@node1 scripts]# 

    2、在node1上远程执行node2的uptime命令

    1)不需要输入yes/or

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

    测试脚本

    [root@node1 scripts]# expect expect1.exp   #使用expect命令来执行expect脚本,相当于bash;或者./expect1.exp(需要x权限)
    spawn ssh root@192.168.32.212 uptime
    root@192.168.32.212's password: 
     19:25:08 up 27 days, 22:14,  0 users,  load average: 0.00, 0.01, 0.05

    2)输入yes/no

    #! /usr/bin/expect
    spawn ssh root@192.168.32.212 uptime
    expect {
    "*yes/no" { send "yes
    "; exp_continue }
    "*password:" { send "123456
    " }
    }
    expect eof

    测试脚本

     
    [root@node1 scripts]# expect expect2.exp 
    spawn ssh root@192.168.32.212 uptime
    The authenticity of host '192.168.32.212 (192.168.32.212)' can't be established.
    ECDSA key fingerprint is SHA256:ObDtb5FnND2UfusUNwcwuhGZJnTHpUNRrIcruOi1p7c.
    ECDSA key fingerprint is MD5:cc:ea:8a:39:a9:c3:10:af:a8:73:01:bf:41:c6:07:48.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added '192.168.32.212' (ECDSA) to the list of known hosts.
    root@192.168.32.212's password: 
     21:04:30 up 27 days, 23:53,  0 users,  load average: 0.00, 0.01, 0.05

    四、expect脚本说明

    1)在Expect自动交互程序执行的过程中,spawn命令是一开始就需要使用的命令,通过spawn执行一个命令或程序,之后所有的Expect操作都会在这个执行过的命令或程序进程中进行,包括自动交互功能,因此如果没有spawn命令,Expect程序将会无法实现自动交互。
    
    2)在Expect自动交互程序的执行过程中,当使用spawn命令执行一个命令或程序之后,会提示某些交互式信息,expect命令的作用就是获取spawn命令执行后的信息,看看是否和其事先指定的相匹配,一旦匹配上指定的内容就执行expect后面的动作,expect命令也有一些选项,相对用得较多的是-re,表示使用正则表达式的方式来匹配。

    1、把expect与send放在一行

     
    #!/usr/bin/expect                      #<==脚本解释器。
    spawn ssh root@192.168.32.212 uptime   #<==开启expect自动交互式,执行ssh命令。
    expect "*password" {send  "123456
    "}  #<==如果ssh命令输出匹配*password,就发送123456给系统。
    expect eof    #<==要想输出结果,还必须加eof,表示expect结束。

    2、expect和send放在不同行

    #!/usr/bin/expect
    spawn ssh root@192.168.33.130 uptime
    expect "*password:"
    send  "123456
    "
    expect eof

    3、多次匹配不同的字符串

    expect命令还有一种高级用法,即它可以在一个expect匹配中多次匹配不同的字符串,并给出不同的处理动作,此时只需要将匹配的所有字符串放在一个{}(大括号)中就可以了,当然还要借助exp_continue指令实现继续匹配。

    #! /usr/bin/expect
    spawn ssh root@192.168.32.212 uptime
    expect {
    "*yes/no" { send "yes
    "; exp_continue }
    "*password:" { send "123456
    " }
    }
    expect eof
    #注意:
    1)exp_send和send类似,后面的
    (回车)和前文的
    (换行)类似。
    2)expect{},类似多行expect。
    3)匹配多个字符串,需要在每次匹配并执行动作后,加上exp_continue,最后一个匹配的字符串除外。

    4、利用expect响应Shell脚本中的多个read读入

    [root@node1 scripts]# cat test.sh   #<==这里是Shell脚本!
    #!/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 "your email is $mail."

    执行结果如下:

    [root@node1 scripts]# sh test.sh 
    Please input your username:ywx        #<==提示输入,只能手动输入对应字符串。
    Please input your password:123456         #<==提示输入,只能手动输入对应字符串。
    Please input your email:441520481@qq.com   #<==提示输入,只能手动输入对应字符串。
    your name is ywx,your password is 123456,your email is 441520481@qq.com.

    开发Expect自动化脚本,根据需求自动输入多个字符串。

     
    #!/usr/bin/expect
    spawn /bin/sh /script/test.sh   #<==执行上述Shell脚本,注意这里使用的是相对路径。
    expect {
        "username" {exp_send "ywx
    ";exp_continue}
                              #<==若获取到的是username信息,则自动输入oldboy。
        "*pass*"   {send "123456
    ";exp_continue}
                              #<==若获取到的是*pass*信息,则自动输入123456。
        "*mail*"   {exp_send "441520481@qq.com
    "}
                              #<==若获取到的是*mail*信息,则自动输入邮件地址。
    }
    expect eof

    执行结果如下:

    [root@oldboy script]# expect expect4.exp      #<==回车后,无任何人工交互,直接输出结果。
    spawn /bin/sh /script/test.sh
    Please input your username:ywx       #<==自动输入需要的字符串。
    Please input your password:123456       #<==自动输入需要的字符串。
    Please input your email:441520481@qq.com #<==自动输入需要的字符串。
    your name is ywx,your password is 123456,your email is 441520481@qq.com

    5、send

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

    #!/usr/bin/expect
    spawn /bin/sh /script/test.sh   #<==执行上述Shell脚本,注意这里使用的是相对路径。
    expect {
        "username" {exp_send "ywx
    ";exp_continue}
                              #<==若获取到的是username信息,则自动输入oldboy。
        "*pass*"   {send "123456
    ";exp_continue}
                              #<==若获取到的是*pass*信息,则自动输入123456。
        "*mail*"   {exp_send "441520481@qq.com
    "}
                              #<==若获取到的是*mail*信息,则自动输入邮件地址。
    }
    expect eof

    send命令可用的参数

     
    ·-i:指定spawn_id,用来向不同的spawn_id进程发送命令,是进行多程序控制的参数。
    ·-s:s代表slowly,即控制发送的速度,使用的时候要与expect中的变量send_slow相关联。

    6、exp_continue命令

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

    #!/usr/bin/expect
    spawn /bin/sh /script/test.sh   #<==执行上述Shell脚本,注意这里使用的是相对路径。
    expect {
        "username" {exp_send "ywx
    ";exp_continue}
                              #<==若获取到的是username信息,则自动输入oldboy。
        "*pass*"   {send "123456
    ";exp_continue}
                              #<==若获取到的是*pass*信息,则自动输入123456。
        "*mail*"   {exp_send "441520481@qq.com
    "}
                              #<==若获取到的是*mail*信息,则自动输入邮件地址。
    }
    expect eof

    7、send_user命令

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

    #!/usr/bin/expect
    send_user "I am ywx.
    "      #<==
    表示换行。
    send_user "I am a linuxer,	"   #<==	表示Tab键。
    send_user "My blog is http://kingseal.top
    "

    测试脚本

     

    [root@node1 scripts]# expect expect4.exp
    I am ywx.
    I am a linuxer, My blog is http://kingseal.top

    8、exit命令

    exit命令的功能类似于Shell中的exit,即直接退出Expect脚本,除了最基本的退出脚本功能之外,还可以利用这个命令对脚本做一些关闭前的清理和提示等工作。

     
    #!/usr/bin/expect
    send_user "I am ywx.
    "      #<==
    表示换行。
    send_user "I am a linuxer,	"   #<==	表示Tab键。
    send_user "My blog is http://kingseal.top
    "
    exit -onexit {
      send_user "Good bye.
    "
    }

    测试脚本

    [root@node1 scripts]# expect expect5.exp
    I am ywx.
    I am a linuxer, My blog is http://kingseal.top
    Good bye.

    五、Expect常用命令总结

    六、Expect程序变量

    1、Expect程序变脸定义

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

    set 变量名  变量值

    打印变量的基本语法:

     
    puts $变量名
    send_user $变量名

    案例

    #!/usr/bin/expect
    set PWD "123456"
    puts $PWD
    send_user "$PWD
    "

    测试脚本

    [root@node1 scripts]# expect expect6.exp 
    123456
    123456

    2、expect特殊参数变量

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

    1、在Expect中$argv表示参数数组,可以使用[lindex$argv n]接收Expect脚本传参,n从0开始,分别表示第一个[lindex$argv 0]参数、第二个[lindex$argv 1]参数、第三个[lindex$argv 2]参数……
    2、Expect接收参数的方式和bash脚本的方式有些区别,bash是通过$0……$n这种方式来接收的,而Expect是通过set<变量名称>[lindex$argv<param index>]来接收的。
    3、除了基本的位置参数外,Expect也支持其他的特殊参数,例如:$argc表示传参的个数,$argv0表示脚本的名字。

    案例

    #!/usr/bin/expect
    #define var
    set file [lindex $argv 0]     #<==相当于Shell里脚本传参的$1。
    set host [lindex $argv 1]     #<==相当于Shell里脚本传参的$2。
    set dir  [lindex $argv 2]     #<==相当于Shell里脚本传参的$3。
    send_user "$file	$host	$dir
    "
    puts "$file	$host	$dir
    "

    测试脚本

     
    [root@node1 scripts]# expect expect7.exp /etc/hosts 192.168.32.211 /tmp
    /etc/hosts    192.168.32.211    /tmp
    /etc/hosts    192.168.32.211    /tmp

    针对Expect脚本传参的个数及脚本名参数

    #!/usr/bin/expect
    set file [lindex $argv 0]
    set host [lindex $argv 1]
    set dir  [lindex $argv 2]
    puts "$file	$host	$dir"
    puts $argc   #参数个数等于bash中的$#
    puts $argv0  #等于bash中的$0

    测试脚本

    [root@node1 scripts]# expect expect8.exp /etc/hosts 192.168.32.211 /tmp
    /etc/hosts    192.168.32.211    /tmp
    3
    expect8.exp

    七、Expect程序中的if条件语句

    1、Expect程序中if条件语句的基本语法

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

    2、案例

    1、使用if语句判断脚本传参的个数,如果不符则给予提示。

    #!/usr/bin/expect
    if { $argc!= 3 } {      #<==$argc为传参的个数,相当于Shell里的$#。
        send_user "usage:expect $argv0 file host dir
    " #<==给予提示,$argv0代表脚本的名字。
        exit                                            #<==退出脚本。
    }
    #define var
    set file [lindex $argv 0]
    set host [lindex $argv 1]
    set dir  [lindex $argv 2]
    puts "$file	$host	$dir"

    测试

    [root@node1 scripts]#  expect expect9.exp
    usage:expect expect9.exp file host dir  #<==expect9.exp就是$argv0输出的结果。
    [root@node1 scripts]#  expect expect9.exp /etc/hosts 192.168.32.212 /home/ywx  #<==传三个参数。
    /etc/hosts     192.168.32.212  /home/ywx #<==这是脚本后面的三个参数。

    2、使用if语句判断脚本传参的个数,不管是否符合都给予提示

     
    #!/usr/bin/expect
    if {$argc!= 26} {
        puts "bad."
    } else {
    puts "good."
    }

    测试脚本

    [root@node1 scripts]# expect expect10.exp
    bad.
    [root@node1 scripts]# expect expect10.exp {a..z}
    good.

    八、Expect中的关键字

    1、eof关键字

    eof(end-of-file)关键字用于匹配结束符,前面已经使用过eof这个关键字了

    #!/usr/bin/expect
    spawn /bin/sh /script/test.sh   #<==执行上述Shell脚本,注意这里使用的是相对路径。
    expect {
        "username" {exp_send "ywx
    ";exp_continue}
                              #<==若获取到的是username信息,则自动输入oldboy。
        "*pass*"   {send "123456
    ";exp_continue}
                              #<==若获取到的是*pass*信息,则自动输入123456。
        "*mail*"   {exp_send "441520481@qq.com
    "}
                              #<==若获取到的是*mail*信息,则自动输入邮件地址。
    }
    expect eof

    2、timeout关键字

    timeout是Expect中的一个控制时间的关键字变量,它是一个全局性的时间控制开关,可以通过为这个变量赋值来规定整个Expect操作的时间,注意这个变量是服务于Expect全局的,而不是某一条命令,即使命令没有任何错误,到了时间仍然会激活这个变量,此外,到时间后还会激活一个处理及提示信息开关。

    #!/usr/bin/expect
    spawn ssh root@192.168.32.212 uptime
    set timeout 30     #<==设置30秒超时。
    expect "yes/no"    {exp_send "yes
    ";exp_continue}
    expect  timeout    {puts "Request timeout by ywx.";return}
                       #<==当到达30秒后就超时,打印指定输出后退出。

    测试脚本

    [root@node1 scripts]# expect expect11_.exp
    spawn ssh root@192.168.32.212 uptime
    root@192.168.32.212's password:Request timeout by ywx.

    上面的处理中,首先将timeout变量设置为30秒,此时Expect脚本的执行只要超过了30秒,就会直接执行结尾的timeout动作,打印一个信息,停止运行脚本。

    在expect{}的用法中,还可以使用下面的timeout语法:

     
    #!/usr/bin/expect
    spawn ssh root@192.168.32.212 uptime
    expect {
        -timeout 3
        "yes/no" {exp_send "yes
    ";exp_continue}
        timeout  {puts "Request timeout by ywx.";return}
    }

    测试脚本

     
    [root@node1 scripts]# expect expect12.exp
    spawn ssh root@192.168.32.212 uptime
    root@192.168.32.212's password:Request timeout by ywx.

    timeout变量设置为0,表示立即超时,为-1则表示永不超时。

    九、企业生产场景下的Expect案例

    1、开发Expect脚本实现自动交互式批量执行命令。

    #!/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 "123456"
    #
    spawn ssh root@$ip $cmd
    expect {
        "yes/no"    {send "yes
    ";exp_continue}
        "*password" {send "$password
    "}
    }
    expect eof

    测试脚本

    [root@node1 scripts]# expect expect13.exp 192.168.32.212 uptime
    spawn ssh root@192.168.32.212 uptime
    root@192.168.32.212's password:
     142053 up 1626,  2 users,  load average:0.07, 0.03, 0.01
    [root@node1 scripts]# expect expect13.exp 192.168.32.212 "free -m"
    spawn ssh root@192.168.32.212 free -m
    root@192.168.32.212's password:
                 total       used       free     shared    buffers     cached
    Mem:          981        492        488          0         42        293
    -/+ buffers/cache:       156        824
    Swap:         767          0        767

    2、利用Shell循环执行Expect脚本命令

    #!/bin/sh
    if [ $# -ne 1 ]
      then
        echo "USAGE:$0 cmd"
        exit 1
    fi
    cmd=$1
    for n in 128 129 130
    do
      expect /script/expect13.exp 192.168.32.$n "$cmd"  #<==带双引号接收带参数的命令。
    done

    expect13.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 "123456"
    #
    spawn ssh root@$ip $cmd
    expect {
        "yes/no"    {send "yes
    ";exp_continue}
        "*password" {send "$password
    "}
    }
    expect eof

    3、开发Expect脚本以实现自动交互式批量发送文件或目录。

    1)实现Expect自动交互的脚本:

    [root@node1 scripts]# cat 18_13_1.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 "123456"
    spawn scp -P22 -rp $file root@$host:$dir
    expect {
        "yes/no"    {send "yes
    ";exp_continue}
        "*password" {send "$password
    "}
    }
    expect eof

    2)利用Shell循环执行Expect脚本命令

    [root@node1 scripts]# cat 18_13_2.sh
    #!/bin/sh
    if [ $# -ne 2 ]
      then
        echo $"USAGE:$0 file dir"
        exit 1
    fi
    file=$1
    dir=$2
    for n in 128 129 130
    do
        expect 18_13_1.exp $file 192.168.32.$n  $dir
    done
     

     

    I have a dream so I study hard!!!
  • 相关阅读:
    计算机视觉(ComputerVision, CV)相关领域的站点链接
    JS-网页中分页栏
    国内搜索大哥iOS面试题
    【iOS与EV3混合机器人编程系列之中的一个】iOS要干嘛?EV3能够更酷!
    Android多线程.断点续传下载
    【转】BeyondCompare软件使用
    【转】目前世界上最为流行的代码托管网站
    【转】为什么要用GIT而不是SVN
    【转】UBUNTU 下GIT的安装
    【转】雄鹰计划-卓越工程师炼成记
  • 原文地址:https://www.cnblogs.com/yaokaka/p/13846407.html
Copyright © 2011-2022 走看看