zoukankan      html  css  js  c++  java
  • expect 知识与示例说明

    expect 知识与示例说明

    2012/04/10 chenxin
    2019/07/07 update Chenxin

    参考
    https://www.cnblogs.com/yinghao1991/p/6926125.html
    https://www.jellythink.com/archives/373
    https://www.cnblogs.com/chengjian-physique/p/8254381.html
    https://core.tcl-lang.org/expect/index
    概念与基础知识
    目的
    利用expect,则可以根据程序的提示,模拟标准输入提供给程序,从而实现自动化交互执行.
    指令
    命令 作用
    send 用于向进程发送字符串.如果要发送Ctrl-C结束进程,可以通过send "03" 实现。send 01(发送ctrl+a) 然后 send "d" (发送d)合起来相当于发送ctrl+a +d.
    expect 从进程接收字符串(等待一个进程的反馈,捕获后匹配)
    spawn 启动新的进程(spawn后的send和expect命令都是和使用spawn打开的进程进行交互).
    interact 进入用户交互(一般情况下使用spawn、send和expect就可以很好的完成任务;在特殊场合下需使用interact,用于退出自动化,进入人工交互。如我们用spawn、send和expect完成了ftp登陆下载文件任务,但是我们希望在文件下载结束后,仍可以停留在ftp命令行状态,以便手动的执行后续命令,此时就需要使用interact).
    timeout expect中等待命令的输出信息是有一个 timeout的设定的,默认是10秒。这个特性是防止那些执行死机的命令的。一旦到了这个timeout,还是没有屏幕输出的话,expect脚本中下面的代码就会执行 。或者我们在expect脚本中如果定义了timeout的响应代码的话,这些代码就会被执行。
    示例说明
    示例一

    !/usr/tcl/bin/expect # 使用expect来解释该脚本;

    set timeout 30 # 设置超时时间,单位为秒,默认情况下是10秒;
    set host "101.200.241.109" # 设置变量;
    set username "root"
    set password "123456"
    spawn ssh $username@$host # spawn是进入expect环境后才可以执行的expect内部命令.它主要的功能是给ssh运行进程加个壳,用来传递交互指令;
    expect "password" {send "$password "} # 这里的expect也是expect的一个内部命令.判断上次输出结果里是否包含“password”的字符串,如果有则立即返回;否则就等待一段时间后返回(上面设置的30s);send "$password ":当匹配到对应的输出结果时,就发送密码到打开的ssh进程,执行交互动作;这就是expect的 "模式-动作"
    interact # 执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。
    示例二
    set timeout -1
    spawn ftp ftp.test.com #打开新的进程,该进程用户连接远程ftp服务器
    expect "Name" #进程返回Name时
    send "user " #向进程输入user
    expect "Password:"
    send "123456 "
    expect "ftp> "
    send "binary "
    expect "ftp> " #进程返回ftp>时
    send "get test.tar.gz " #向进程输入get test.tar.gz

    模式-动作 与 exp_continue循环式匹配
    结合着expect "password" {send "$password "}这句代码来说说“模式-动作”。这是一个单一分支模式匹配.简单的说就是匹配到一个模式,就执行对应的动作;匹配到password字符串,就输入密码。
    单一分支模式语法:
    expect "hi" {send "You said hi"} # 匹配到hi后,会输出"you said hi"
    多分支模式语法:
    expect "hi" { send "You said hi " }
    "hello" { send "Hello yourself " }
    "bye" { send "That was unexpected " }
    匹配到hi,hello,bye任意一个字符串时,执行相应的输出。等同于如下写法:
    expect {
    "hi" { send "You said hi "}
    "hello" { send "Hello yourself "}
    "bye" { send "That was unexpected "}
    }
    你可能也会看到这样的代码:
    expect {
    "password" {
    send "$password ";exp_continue # 其中exp_continue表示循环式匹配
    }
    eof # 一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束
    {
    send "eof"
    }
    }
    通常匹配之后都会退出语句,但如果有exp_continue则可以不断循环匹配,输入多条命令,简化写法。
    关于 exp_continue 的说明
    exp_continue 附加于某个 expect 判断项之后,可以使该项被匹配后,还能继续匹配该 expect 判断语句内的其他项。exp_continue 类似于控制语句中的 continue 语句。
    例如:下例将判断交互输出中是否存在 yes/no 或 *assword。如果匹配 yes/no 则输出 yes 并再次执行判断;如果匹配 assword 则输出 123abc 并结束该段 expect 语句。
    expect {
    "yes/no" {send "yes "; exp_continue;}
    "
    assword" {set timeout 300; send "123abc ";}
    }
    注意:exp_continue允许expect继续执行自身而不是往下执行.默认情况下,exp_continue会重置timeout,如果不想重置timeout,使用-continue_timer选项。
    expect eof 说明
    expect {
    "password" {
    send "$password "
    exp_continue # 其中exp_continue表示循环式匹配
    }
    eof # 一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束
    {
    send "eof"
    }
    }
    interact: 执行完成后保持交互状态,把控制权交给控制台(可以手工操作了)。若没有这句,登录完成后就会退出,而不是留在远程终端上。如果你只是登录过去执行一段命令就退出,可改为[expect eof]
    expect eof: 传输一个大文件,脚本最后通过expect eof由于expect eof的超时时间很短,默认10秒,因此很可能导致文件传输不完整,解决方法是:将expect eof改成 expect -timeout -1 eof
    expect puts 用法
    puts输出到控制台,或输出到文件.
    在shell中嵌套的时候,用puts的话,要注意变量被shell自动展开的问题.调用的时候应该采用以下方式:

    !/bin/bash

    ...
    puts "i is $i" # 要""括起来,shell中嵌套的时候,不应该直接puts $i
    ...
    传参(命令行里的参数传递到expect脚本内部)
    很多时候,我们需要传递参数到脚本中,现在通过下面这段代码来看看如何在expect中使用参数:

    !/usr/tcl/bin/expect

    if {$argc < 3} { # $argc表示参数个数
    puts "Usage:cmd "
    exit 1
    }
    set timeout -1
    set host [lindex $argv 0] # 第一个参数
    set username [lindex $argv 1]
    set password [lindex $argv 2]
    spawn ssh $username@$host
    expect "password" {send "$password "}
    interact
    在expect中,$argc表示参数个数,而参数值存放在$argv中,比如取第一个参数就是[lindex $argv 0],以此类推。
    expect内部变量<$expect_out()>
    <$expect_out(buffer)>存储了所有对expect的输入(捕获到的所有内容).
    <$expect_out(0,string)>存储了匹配到expect参数的输入(能够匹配到的内容).
    比如如下程序:
    expect "hi "
    send "you typed <$expect_out(buffer)>"
    send "but I only expected <$expect_out(0,string)>"
    当在标准输入中输入
    test
    hi
    时,运行结果如下
    you typed: test
    hi
    I only expect: hi
    shell中嵌套expect 以及 bash与expect 彼此间变量传递说明
    2019/07/07 update Chenxin
    cat test.sh

    !/bin/bash

    backup_aws="3.1.250.30";
    passwd="cx88...adm!@#"
    expect <<EOF
    set timeout 5
    spawn ssh admin@$backup_aws -p 4399
    expect {
    "refused" {exit 1}
    "unreachable" {exit 2}
    "No route" {exit 3}
    "yes/no" {send "yes ";exp_continue}
    "password:" { send "$passwd ";sleep 1 }
    "
    ]$" {exit 0}
    "
    ]#" {exit 0}
    "
    @" {exit 0}
    "timeout" {exit 4}
    eof {exit 5}
    }
    expect "
    ]$" {send "cd /home/admin/tmp ";}
    #expect "
    ]$" {send "mkdir test_dir "; }
    expect "
    ]$" {send "cd ../ ";}
    send "echo hello ";send "echo OK "; # dont expect, send immediately
    expect "
    ]$" {send "tail -n 2 uwsgi8000.log ";sleep 1} # The info of out maybe need some secends for display.
    expect "
    ]$*" {send "cd /opt;ls ";sleep 1}
    interact
    EOF
    注意,即使本地执行,有时候也需要多尝试sleep 1,等待1秒的情况,否则输出还没来得及看到,就结束了.
    bash 与 expect 互相使用彼此变量
    如果是在shell中启动了expect脚本,现在想在expect中使用shell里的变量,可以有两种方法:
    1.首先在shell中进行变量export, 例如export a=1, 然后在expect中通过 $::env(a) 引用,例如set a_exp $::env(a)
    2.也可以通过执行子shell调用,例如: set a [exec sh -c {echo $a}]
    如果是在expect里面执行shell,并且想在shell里面使用expect的变量,需要在expect里面设置环境变量:
    例如:set ::env(LAB) my_lab
    那么在shell语句里面可以通过$LAB引用。
    附一个完整的主机认证脚本
    SSH主机自动认证expect
    20121119 Chenxin

    !/bin/bash

    function renzheng () {
    backup_aws="$1";
    expect <<EOF
    set timeout 5
    spawn ssh -i /home/rsyncfiles/.ssh/id_dsa rsyncfiles@$backup_aws -p 4399 echo
    expect {
    "refused" {exit 1}
    "unreachable" {exit 2}
    "No route" {exit 3}
    "yes/no" {send "yes ";exp_continue}
    "]$" {exit 0}
    "]#" {exit 0}
    "@" {exit 0}
    "timeout" {exit 4}
    eof {exit 5}
    }
    EOF
    }
    expect命令行指令使用方式
    https://www.cnblogs.com/yinghao1991/p/6926125.html
    expect命令语法
    expect [选项] [ -c cmds] [ [ -[f|b] ] cmdfile] [ args]
    可以参考本笔记后面的一些示例脚本的用法.
    数字天空游戏服上一些脚本
    redis 的启动与停止
    在游戏服启动之前,需要先启动 redis服务,如果是关停,则先停止游戏服再停止 redis服;
    redis的每次关停后,都会执行备份物理文件操作,放置到 /home/lzadmin/redis_data_bak/下,并清除45天前的备份文件;
    cat /usr/local/bin/lzll01_redis_server_manage.exp

    !/bin/bash

    manage lzll redis server,include the gs,redis cmd,gs log...,except the db script;

    By Chenxin 20120727

    引入系统环境变量#

    if [ -f /etc/init.d/functions ];then
    . /etc/init.d/functions;
    fi
    curr_date=date +%Y%m%d%H%M
    export TERM=vt100
    cd /home/sftproot/home/;
    find /home/sftproot/home/lzupdate/ -maxdepth 2 -name redis__ |awk -F "/" '{print $7}'|grep -v ".sh">/home/lzadmin/redis_server_list.tmp
    cd /home/lzadmin/;

    start the redis server;

    function start_redis_server () {
    cd /home/lzadmin/;
    for i in cat redis_server_list.tmp
    do {
    cd /home/sftproot/home/lzupdate/redis_servers/$i
    sudo /home/sftproot/home/lzupdate/redis_servers/$i.sh /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf
    } done
    }

    stop the redis server;

    function stop_redis_server () {
    cd /home/lzadmin/;
    for i in cat redis_server_list.tmp
    do {
    cd /home/sftproot/home/;
    redis_pass=cat /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf |grep requirepass|awk '{print $2}'
    redis_port=cat /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf |grep port|awk '{print $2}'
    expect -c "
    set timeout 20
    eval spawn screen -S $i
    sleep 2
    send "/usr/local/redis-2.4.10/src/redis-cli -p $redis_port "
    expect ">";send "auth $redis_pass ";
    expect "OK";send "save ";
    expect "OK";send "shutdown ";
    expect "
    >";send "exit ";
    sleep 2
    send "04"
    sleep 2
    send "04"
    expect eof"
    # 备份物理文件
    mkdir -p /home/lzadmin/redis_data_bak # 每次停服都备份一次 redis物理数据文件到lzadmin目录下
    cp -aprf /home/sftproot/home/lzupdate/redis_servers/$i/data/dump.rdb /home/lzadmin/redis_data_bak/dump_$curr_date.rdb
    find /home/lzadmin/redis_data_bak/ -mtime +45 -name "*.rdb" -exec rm -rf {} ;
    } done
    }
    case $1 in
    start)
    start_redis_server;;
    stop)
    stop_redis_server;;
    restart)
    stop_redis_server;
    start_redis_server;;
    *)
    echo "(Arguments shuld be start|stop|restart,OK?!)" ;;
    esac
    游戏服务的启动与关停
    cat /usr/local/bin/lzll02_game_server_manage.exp

    !/bin/bash

    ...
    cd /home/lzadmin/;
    function start_game_and_log_server () {
    cd /home/lzadmin/;
    #start the game server;
    for i in cat game_server_list.tmp
    do {
    expect -c "
    set timeout 60
    eval spawn screen -S $i
    sleep 2
    send "cd /home/sftproot/home/lzupdate/game_servers/$i/ "
    send "sudo /home/sftproot/home/lzupdate/game_servers/$i/GameServer.sh "
    expect "cmd>"
    sleep 2
    send "01"
    send "d"
    expect eof"
    } done
    #start the game server log to file;
    for i in cat game_server_list.tmp
    do {
    expect -c "
    set timeout 60
    eval spawn screen -S ws_$i
    sleep 2
    send "cd /home/sftproot/home/lzupdate/game_servers/$i/ "
    send "sudo /home/sftproot/home/lzupdate/game_servers/$i/WriteLogServer.sh "
    expect "cmd>"
    sleep 2
    send "01"
    send "d"
    expect eof"
    } done
    }
    function stop_game_and_log_server () {
    cd /home/lzadmin/;
    #stop the game server;
    for i in cat game_server_list.tmp
    do {
    expect -c "
    set timeout 60
    eval spawn screen -r $i
    send "stop "
    expect "application will exit"
    sleep 10
    send "04"
    expect eof"
    } done
    #stop the game log service;
    for i in cat game_server_list.tmp
    do {
    expect -c "
    set timeout 60
    eval spawn screen -r ws_$i
    send "stop "
    expect "TaskLog finished" # 捕获到该字符后 ,需要等待10s ,否则如果直接发送 ctrl+d的话,会造成故障;
    sleep 10
    send "04"
    expect eof"
    } done
    }
    case $1 in
    start)
    start_game_and_log_server;;
    stop)
    stop_game_and_log_server;;
    restart)
    stop_game_and_log_server;
    start_game_and_log_server;;
    *)
    echo "(Arguments shuld be start|stop|restart,OK?!)" ;;
    esac
    批量获取服务器序列号
    2017/04/20 Chenxin

    !/bin/bash

    passwd_admin=yRtMHiNc0A5JNnz#
    passwd_root=Ywsz098xylz!@#
    for i in cat ip.txt
    do {
    expect << EOF
    set timeout 1
    spawn ssh -p 4399 admin@$i
    expect {
    "yes/no" { send "yes "; exp_continue }
    "password:" { send $passwd_admin ;sleep 1;exp_continue }
    "密码" { send $passwd_admin ;sleep 1;exp_continue }
    "口令" { send $passwd_admin ;sleep 1;exp_continue }
    "admin@" {
    send "su - ";sleep 1;
    expect {
    "password:" { send $passwd_root ;sleep 1;exp_continue }
    "密码:" { send $passwd_root ;sleep 1;exp_continue}
    "口令" { send $passwd_root ;sleep 1;exp_continue}
    "root@" { send "echo -n $i && dmidecode -t 1|grep 'Serial Number' " }
    }
    }
    }
    expect eof
    EOF
    } done

  • 相关阅读:
    SpringBoot 集成Hystrix熔断
    windows10 个性化启动Python,cmd窗口显示启动名称
    Web前端 table去掉td边框大小及颜色
    Windows 10 运行.bat文件启动Jar项目
    SpringBoot Feign接口方式调用服务
    SpringBoot Ribbon负载均衡策略配置
    SpringBoot Eureka集群配置
    SpringBoot集成Eureka
    面试题 16.01. 交换数字
    1476. 子矩形查询
  • 原文地址:https://www.cnblogs.com/chanix/p/11732226.html
Copyright © 2011-2022 走看看