expect是基于Tcl (tool command language)语言开发的,主要应用于自动化交互式操作的场景,借助expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
常用指令
spawn:交互程序开始后面跟命令或者指定程序(在壳内启动这个进程)expect:获取匹配信息匹配成功则执行expect后面的程序动作(检测由壳内进程发出的特定交互指令反馈字符串后向下执行)send:用于向进程发送字符串(从壳外向壳内进程发送一条字符串,换行符为确认结束)interact:运行用户交互exp_continue:在expect中多次匹配就需要用到send_user:用来打印输出,相当于shell中的echoexit:退出expect脚本eof:expect执行结束,退出set:定义变量puts:输出变量
命令实例
简单例子
先看一个简单例子,就是自动登录到网络设备msr3600
cat /root/expect/ssh.tcl
#!/usr/bin/expect
spawn ssh xxx@192.168.1.2
expect "password"
send "xxx"
expect ">"
send "quit\n"
expect eof
# 执行
expect ssh.tcl
可以自动登录然后再退出
send命令
send命令接收一个字符串参数,并将该参数发送到进程(前提是先使用spawn开启进程)
上面的示例中,有2处使用了send
其实就是spawn打开了一次ssh连接以后,会要求我们输入登录密码,
第一个send就是将密码发送到spawn的进程中。
第二个send就是退出ssh的意思。
expect
expect命令和send命令正好相反,expect通常是用来等待一个进程的反馈。
expect可以接收一个字符串参数,也可以接收正则表达式参数。
比如root登录之后,界面会输出一个#,那么expect此时匹配的是这个#。
ssh登录后,一般shell会返回一个password:的输出,那么此时可以匹配password的字符。
而如果我们没有通过spawn开启一个ssh或者类似的进程,而是直接在expect程序里面expect一个字符串的时候,会怎么样?
#!/bin/bash/expect
expect "hello" { send "hello world\n" }
这时候,当我们执行脚本的时候,会发现,除非在键盘上敲入hello然后确认,才会输出hello world
expect用法是匹配到指定的字符时,执行指定的动作。匹配有2种匹配方式:
单一分支匹配
类似与上面的例子:
expect "hello" { send "hello world\n" }
单一匹配就是只有一种匹配情况。
有点类似于普通编程语言的if语句,只有一个条件的情况。
多分支匹配模式
#!/usr/bin/expect
set timeout 5
expect {
"hello" {send "hello world\n"}
"hi" {send "hi world"}
"bye" {send "bye world"}
}
我们发现,expect语言都是用{}来做代码分割和代码块分割的。
spawn
最开始的ssh案例里面,我们使用spawn开启了一个ssh进程,然后使用send输入密码。
我们在加多写命令,查看登录设备的version:
#!/usr/bin/expect
spawn ssh xxx@x.x.x.x
expect "password"
send "xxx\n"
expect ">"
send "system-view\n"
expect "]"
send "display version\n"
send "exit\n"
send "quit\n"
expect eof
spawn开启一个ssh以后,会进入到网络设备的shell登录环境下,
这时候向进程发送一个display version这样的字符串,网络设备就能够识别这是一个有意义的指令,并返回指令的结果。
interact
上述的例子都是自动完成了一些动作。
有时候我们希望停留在界面,等待人工操作的情况。
这时候,我们可以用interact指令,来等待人工干预
#!/usr/bin/expect
spawn ssh xxx@x.x.x.x
expect "password"
send "xxx\n"
expect ">"
send "system-view\n"
expect "]"
interact
该例子执行完system-view以后,会停留在expect打开的ssh界面,等待人工操作。
set
该指令是用来设置变量值。比如:
#!/usr/bin/expect
set uname xxx
set pwd yyy
spawn ssh ${uname}@x.x.x.x
expect "password"
send "${pwd}\n"
expect ">"
send "quit\n"
expect eof
传参
很多场景下,我们写一个脚本都是要传递参数的。
expect也不例外。
expect 有2个内置变量:argc和argv。
argc表示参数的数量,类似于普通shell脚本的#。
argv则可以给自身传递一个参数,以便脚本取出指定位置的参数。例如:
#!/usr/bin/expect
set uname [lindex $argv 0]
set pwd [lindex $argv 1]
puts "$argc"
puts "$argv0"
spawn ssh ${uname}@x.x.x.x
expect "password"
send "${pwd}\n"
expect ">"
send "quit\n"
expect eof
我们可以在运行脚本时传入2个参数
expect ssh4.tcl xxx yyy
expect还有一个非常重要的内置变量,arvg0。
argv是存储了所有传递进来的参数的变量。
而argv0则是脚本的名称。这个和shell脚本的$0一个意思。
脚本里面,出现了一个put指令,这个指令是向标准输出输出内容。
从结果看,我们传递了2个参数,puts指令将$argc输出到了屏幕,值是2,表示有2个参数。
也将$argv0脚本名称输出到了屏幕。
同时,通过lindex $argv int,获取到指定位置的参数值。
运算
incr
增量。一般用在数学计算的时候。
语法为:incr arg {step},arg时要增加的参数名,step是增量值,可以不指定,不指定为1。
puts "------incr-------"
set x 10
puts $x
incr x 5
puts $x
# 结果如下:
------incr-------
10
15
其实这里的incr x 5也相当于set x [expr $x + 5]
[]符号
# 访问数组
puts [lindex $argv 1]
# 数学计算的时候
puts [expr $x + 5]
# 分割字符串的时候
set ss "aa,bb,cc,dd"
puts [split $ss ","]
数组
所有传递的参数,被存放在argv中。
其实argv就是数组
数组的定义
数组的定义,需要结合set的指令
set j "a b c d"
数组的所有元素之间,需要用空格隔开。
数组访问
我们使用[lindex $argv 0]的方法,获取了第0个参数。这个正也是数组的访问方法:
#!/usr/bin/expect
set timeout 5
set j "a b c d e"
puts "[lindex $j 2]"
# 遍历数组,可以使用foreach的方法:
set j "a b c d e"
foreach jj $j {
puts "$jj"
}
for循环
puts "------递增-----"
for {set i 0} { $i < 5 } { incr i } {
puts "$i"
}
puts "------递减-----"
for {set k 5} { $k > 0 } { incr k -1 } {
puts "$k"
}
while循环
puts "------while递增-----"
set m 0
while {$m < 5} {
puts "$m"
incr m 2
}
foreach写法
#!/usr/bin/expect
set timeout 5
puts "------遍历argv--------"
foreach arg $argv {
puts "$arg"
}
# 还可以这么遍历
#!/usr/bin/expect
set timeout 5
for {set y 0 } { $y < $argc} {incr y} {
puts "arg $y: [lindex $argv $y]"
}
shell调用expect
cat ssh.sh
#!/usr/bin/bash
ip=$1
pwd=$2
expect << EOF
set timeout 10
spawn ssh ylzw@${ip}
expect "password"
send "${pwd}\n"
expect ">"
send "system-view\n"
expect "]"
send "display version\n"
send "exit\n"
send "quit\n"
expect eof
EOF
echo "end!"
留言