使用expect实现自动交互,shell命令行自动输入

背景

有需求,在允许命令或者脚本跳出交互行,需要进行内容输入,但需要人手动输入,不是很方便,此时可以通过expect来实现自动互动交互。

expect是一个自动交互功能的工具,可以满足代替我们实际工作中需要从终端手动输入某些内容来使得程序或命令继续运行的目的。如安装软件是时的一些提示,ssh远程主机执行命令时需要多次输入密码的情况。

安装expect

  • 安装依赖:yum install tcl -y
  • 安装expect:Centos系统yum install expect -y或Ubuntu系统apt-get install expect -y

一些基本的expect命令

  • spawn :启动新进程,用于执行shell命令;
  • expect :从发起交互的命令的进程接受字符串,用于匹配我们预想的字符串;
  • send :用于向发起交互的命令的进程发送字符串;
  • interact:允许用户交互,即此命令后,交互将不会由expect进行,将交回给用户;

示例

#!/usr/bin/expect

set timeout 30
set host "192.168.200.221"
set username "root"
set password "123456"

spawn ssh [email protected]$host ls
expect "password" {send "$password\r"}
expect eof
interact

#!/usr/bin/expect: 表示使用expect来解释该脚本。

set timeout 30: 表示设置超时时间,这里是表示超时时间为30秒,默认为10秒,用于执行shell命令的时间,如果执行的shell命令时间较长(如传输文件),则需要设置长一点。

set username "root" : 表示设置并定义了变量username,变量值为"root"。

spawn ssh username@[email protected] ls: 表示使用spawn来执行ssh [email protected]$host ls 命令,该命令只有在expect环境里才能执行,所以直接在命令行输入或没有安装expect则会报错,它的主要功能是给它后面的shell命令运行进程加了个壳,进行传递交互的内容,注意,如果用引号将变量引起,将可能导致错误extra characters after close-quote...,如果执行的命令需要用到引号,使用双引号,并使用\转义,但只适用于命令中只有一对引号的情况,如果出现多对引号,将会出现一些奇怪的错误,暂时不知道如何解决。

ssh -l root 192.168.200.118 ‘mysql -uroot -p123456 -e "show datavases;"‘ 命令。只能先登录目标主机,再匹配[email protected]:~#,send发送命令。

#!/usr/bin/expect -f
set timeout -1
spawn ssh [email protected]192.168.200.118
expect -re "password" { send "userpwd123\r" }
expect -re ":~#" { send "mysql -uroot -p123456\r" }
expect -re "mysql>" { send "show databases;\r" }
expect -re "mysql>" { exit }
expect eof

expect "password": 表示从spawn执行的命令的进程里接受字符串,一般是弹出终端的交互行的标准输入提示信息,如需要你确定时的(yes/no?),需要你输入密码的(...password:)。这里因为ssh命令的交互内容是叫你输入密码,交互提示的内容有password,所以这里匹配password。需要注意的是,expect接受的是spawn执行的命令进程中可能出现的字符串,如果你的spawn执行的命令在执行完之后直接没有进程了,那expect也将不能匹配到任何的字符串,如spawn简单的执行ls等命令,这也说明expect多用于需要执行连接的场景。

send "$password\r": 表示当expect命令匹配成功,就把$password发送给spawn执行的命令的进程,完成交互,相当于手动输入$password,这里的\r代表回车,也可以使用\n,记得加上\r或\n,否则脚本将可能会卡死。

expect eof: 表示结束expect,读取到文件结束符 ,当spawn发送指令到终端执行时在返回时被expect捕捉时,在起始会有一个eof,就好比在shell中 cat >>file <<EOF... EOF一样,在结束时也要有eof;expect eof有时间限制,即我们设置的超时时间,默认10秒,不过可能出现的问题是,如果是在传输一个大文件,可能在文件还没传输完成便断开了命令执行,此时需要设置超时时间长一点或 set timeout -1,或将expect eof改成expect -timeout -1 eof

interact: 执行完命令后,控制权交互控制台,此时再有交互,expect将不会进行交互,需要手动进行输入内容交互。如果没有这句,在需要交互的ssh命令执行完毕后将会退出远程,而不是继续保持在远程。

expect参数

  • $argc:表示命令行参数个数
  • [lindex $argv n]:表示index为n的参数(index从0开始计算),index的区间为左闭右开,如[lindex $argv 0]代表命令行输入的第一个参数,[lindex $argv 0 3] 代表命令行输入的第一到第三个参数 。

示例

#!/usr/bin/expect

set host [lindex $argv 0]
set username [lindex $argv 1]
set num $argc
if { num < 3 } {
    ...
}
  • 将第一个命令行参数赋值给变量host,将第二个命令行参数赋值给变量username,将总参数个数赋值给变量num。

expect流程控制

if语句:

if {条件1} {
    expect {
        "(yes/no)"
        {
        send "yes\r"
        expect "*assword:" {send "$password\r"}
        }
        "password"
        {
        send "$password\r"
    }
} else {
    puts "Expect was timeout"
    send_user "Expect was timeout"
}

expect {}: 多行期望,从上往下匹配,匹配成功里面的哪一条,将执行与之的send命令,注意,这里面的匹配字符串只会执行一个,即匹配到的那个,其余的将不会执行,如果想匹配这句命令执行成功后(如登录成功后等待输入的[email protected]:~#)的其他字符,需要另起一个expect命令,并保证不在expect{}里面。

puts与send_user: 打印信息,类似echo

其他:

  • 判断条件用{}包含
  • 花括号与花括号,和括号与控制语句之间需要有空格,否则会报错expect:extra characters after close-brace
  • if右边要有左花括号,else左边要有右花括号,不能单独一行

for语句:

for {set i 0} {$i < 10} {incr i} {
    puts "I inside first loop: $i"
}

while语句:

set i 0
while {$i < 10} {
    puts "I inside third loop: $i"
    incr i
    puts "I after incr: $i"
}

incr: 递增运算符 incr i ,类似++

switch语句:

switch--$var {
    0 {
        语句块
    }
    1 {
        语句块
    }
    ...
}

函数定义和调用:

使用proc定义函数,使用时输入函数名和参数调用

proc test_exp {argv1 argv2} {
    puts "hello:$argv1"
}
test_exp 参数1 参数2

expect数组:

set arr(n) "hello"   # 赋值,arr为数组名
set arr(1) "first"
$arr(1)     # 引用
array size arr   # 查看数组大小
注意:如果是shell中插入的一段expect中想使用数组,需要转义\$,或<<\EOF...EOF

其他的一些内容

  • 使用正则匹配:使用 -re选项,expect -re "\\\[(.*)]" 其中[在expect shell 正则中都有特殊意义,因此要\三次 ,如果spawn执行的命令不能匹配通配符*,需要在spawn 后加 bash -c。
  • expect -i选项:已交互的方式运行expect。
  • expect -D选项:交互式的调试器,类似gdb。
  • expect -c选项:可执行命令的前置符,expect命令可在命令行执行,该选项-c后的命令需要引号引起来,引号内多个命令分号隔开,可使用多次-c选项,空格隔开。
  • expect -f选项:常见于文件第一行,即#!/usr/bin/expect -f ,指定expect读取的expect命令文件,可选项,该选项会将文件一次性全部读取入内存,加上-f选项可以为执行expect提供更多参数。
  • expect -b选项:类似-f选项,只是每次只读取一行,即可以逐行的执行expect。
  • 拼接字符串:使用append命令append "hello"$user",welcome!"
  • sleep:脚本进入睡眠,使用和其他语言一样,直接跟数字即可,单位为秒。
  • exit:退出
  • foreach:对指定集合的每一个元素,依次赋值给变量。
    foreach [变量] {集合} {语句;}
    foreach i {1 2 3} {
        puts $i
    }
    输出:1
         2
         3
  • exp_continue: 循环匹配,通常匹配之后会退出语句,但使用exp_continue 则可以不断循环执行某段语句。
    expect {
        "password" {
            send "$password\r"
            exp_continue    # 不断匹配字符串"password",只要匹配成功就send
        }
    }
    expect eof
  • shell 嵌套使用expect,使用重定向,需要注意EOF之间的互相对应,并且变量需要在shell中定义,否者将会找不到变量,expect引用变量部分将是空内容,如同变量消失。如果想使在expect里定义的变量生效,使用<<\EOF...EOF,或用引号将第一个EOF引起来,即<<"EOF"...EOF,这样expect中set定义的变量,遍历时赋值的变量以及expect数组就都能使用了,但是相对的,shell里定义的变量也就不能使用了。
    #!/bin/bash
    hostname=$1                #接收第一个参数
    password=$2
    /usr/bin/expect <<-EOF    # 重定向到expect,想使用expect中set定义的变量,需要转义\$
    spawn ssh [email protected]${hostname}   # 或使用\EOF,但如果是\EOF,将不能使用Shell的变量
    expect {
            "(yes/no)"
            {
            send "yes\r"
            expect "*assword:" {send "$password\r"}
            }
            "password"
            {
            send "$password\r"
            }
    }
    expect eof
    EOF   # 由于用的-EOF,这里的EOF可以有空格,tab键
    
    /usr/bin/expect <<EOF
    set m_pm(1) "hello"
    set m_pm(2) "world"
    puts "\$m_pm(2)"
    foreach i {1 2 3} {
        puts \$i
    }
    expect eof
    EOF
  • excpet中执行shell语句,exec sh -c {shell语句},多用于赋值变量,需要注意的是,expect里使用exec执行的shell语句,即使有打印和交互内容(echo,read命令)也不会输出到终端,即执行了命令,你并不知道是否出错,也不知道执行结果,如果需要将shell中echo命令打印的内容输出到终端,只能将执行结果赋值给expect变量,再使用puts命令打印出来,但即使这样,也会出现一些莫名头疼的问题,所以尽量不要在expect中调用复杂的shell语句。你也可以使用匹配字符,send “命令\r” 的方式执行shell命令,相当于交互互动,如expect ":~#" { send "ls\r" } 匹配到root登录后的终端待输出状态,send发送ls命令并回车。
    exec sh -c {shell 命令}    # 执行的shell命令即使有打印和需要交互的内容也不会出现在终端
    
    set test_echo [exec sh -c {echo "test"}]
    puts "$test_echo"
  • expect/shell互相使用彼此变量
    • 如果两者在同一文件中,两者只是作为一段语句存在,使用#!/bin/bash解释的shell文件,expect调用shell变量直接$变量,和shell脚本调用变量方式并无异同,使用#!/usr/bin/expect解释的expect脚本文件,shell作为expect文件的语句,如set a [exec sh -c {echo \$LAB}]调用expect变量,需要在expect里面设置环境变量。

      如:set ::env(LAB) my_lab

    • 如果两者是分别为不同文件,expect作为脚本在shell脚本文件中被调用,如./test.excp,首先需要在shell中进行变量export, 例如export a="test", 然后在expect脚本文件中通过 $::env(a) 引用shell脚本文件的变量,例如set a_exp \$::env(a),同时也可以通过执行子shell调用,例如: set a [exec sh -c {echo $a}]

  • 向进程发送Ctcl + c,如果想向远端发送Ctrl-C结束远端进程,可以通过send "\003" 实现。

参考博客:

果冻想-Linux expect详解

taoyuanforrest-expect使用技巧

原文地址:https://www.cnblogs.com/netflix/p/12122735.html

时间: 2024-10-04 18:08:45

使用expect实现自动交互,shell命令行自动输入的相关文章

[转] 关于linux下通过shell命令(自动)修改用户密码

关于linux下通过shell命令(自动)修改用户密码 2012-04-23 18:47:39 分类: 原文地址:关于linux下(自动)修改用户密码 作者:ubuntuer 本文章总结了如何手动.自动修改本地用户密码及远程机器的用户密码.对做自动化测试提供了自动修改用户密码的原理及方法. 修改本地用户密码:1.交互配置本地用户:以root用户: passwd <username>Changing password for user dewang.New UNIX password: BAD

【转】Shell命令行处理快捷键

来源: http://bbs.chinaunix.net/thread-4176195-1-1.html history 显示命令历史列表 ↑(Ctrl+p) 显示上一条命令 ↓(Ctrl+n) 显示下一条命令 !num 执行命令历史列表的第num条命令 !! 执行上一条命令 !?string? 执行含有string字符串的最新命令 Ctrl+r 然后输入若干字符,开始向上搜索包含该字符的命令,继续按Ctrl+r,搜索上一条匹配的命令 Ctrl+s 与Ctrl+r类似,只是正向检索 Alt+<

[原] Android自动打包之命令行打包

Android自动打包流程详细图: 总结为以下几个步骤: 1. 生成R文件 2. Java代码编译成class文件 3. class文件生成dex文件 4. 打包资源 5. 生成apk 6. 创建密匙 7. 签名apk 下面用Ubuntu命令行来打包,流程如下: 1. 生成R文件,命令如下: aapt package -f -m -J $BUILD_SOURCE_PATH/$ANDROID_GEN \ -A $BUILD_SOURCE_PATH/$PACKAGE_RESOURCE_FILE \

windows下shell命令行的常用操作命令

windows下shell命令行的常用操作命令 Command Shell是Windows内置的第一个使用批处理(.bat)文件自动执行日常任务,可以在Command Shell中运行更复杂的脚本 进入d盘直接输入 d: 切换目录 cd 显示目录 dir 进入某个文件夹并打开windows窗口 start ./filepath 远程连接ssh 与linux下一样,直接输入用户名@ip,输入密码即可 显示ip ipconfig Net user 功能:查看本机账户情况 net user xxx 1

Shell 命令行快捷键

在shell命令终端中,Ctrl+n相当于方向向下的方向键,Ctrl+p相当于方向向上的方向键. 在命令终端中通过它们或者方向键可以实现对历史命令的快速查找.这也是快速输入命令的技巧. 在命令终端中可以通过Ctrl+r 实现快速检索使用过的历史命令.Ctrl+r中r是retrieve中r. Ctrl+a:光标回到命令行首. (a:ahead) Ctrl+e:光标回到命令行尾. (e:end) Ctrl+b:光标向行首移动一个字符. (b:backwards) Ctrl+ f:光标向行尾移动一个字

〖Linux〗上传单个文件到FTP的Shell命令行(函数)

1 #!/bin/bash - 2 #=============================================================================== 3 # 4 # FILE: ftp_put_file.sh 5 # 6 # USAGE: ./ftp_put_file.sh 7 # 8 # DESCRIPTION: 9 # 10 # OPTIONS: --- 11 # REQUIREMENTS: --- 12 # BUGS: --- 13 # NO

几种在shell命令行中过滤adb logcat输出的方法

我们在Android开发中总能看到程序的log日志内容充满了屏幕,而真正对开发者有意义的信息被淹没在洪流之中,让开发者无所适从,严重影响开发效率.本文就具体介绍几种在shell命令行中过滤adb logcat输出的方法. 1.只显示需要的输出(白名单) 最方便的当然是通过管道使用 grep 过滤了,这样可以使用 grep 强大的正则表达式匹配.简单的匹配一行当中的某个字符串,例如 MyApp: adb logcat | grep MyApp       adb logcat | grep -i

使用jq工具在Shell命令行处理JSON数据

因为最近要处理一些 JSON 数据格式,一大早经过一番搜索后,最终找到了 jq 这个很棒的工具.jq 允许你直接在命令行下对 JSON 进行操作,包括分片.过滤.转换等等. 首先在mac下安装jq,使用brew install jq就可以了,前提是安装了homebrew,如果在linux ubuntu下,应该可以使用sudo apt-get install jq安装. 让我们通过几个例子来说明 jq 的功能: 一.输出格式化,漂亮的打印效果 如果我们用文本编辑器打开 JSON,有时候可能看起来会

* 和 ?在 shell 命令行中与在正则表达式中的区别

Linux 正则表达式 你有没有想过,在 shell 命令行中的 *,?和正则表达式中的*,?是否一样? 自打好多年前接触 DOS,就知道了* 和?这两个通配符(Wildcard),象 dir *.* 这样的命令也不知道敲了多少遍. 后来,开始用 Windows 3.1 这样的图形界面操作系统,命令行就就得越来越少了. 再后来,开始学习正则表达式,又开始接触 * 和 ? 这样的东西.在正则中它们被称为元字符(Meta Character). 再再后来,开始使用 Linux,于是又开始使用命令行,