0%

【转】Expect脚本使用

expect是Unix系统中用来进行自动化控制和测试的软件工具,由Don Libes制作,作为Tcl脚本语言的一个扩展,应用在交互式软件中如telnet,ftp,Passwd,fsck,rlogin,tip,ssh等等。该工具利用Unix伪终端包装其子进程,允许任意程序通过终端接入进行自动化控制;也可利用Tk工具,将交互程序包装在X11的图形用户界面中。

我们通过Shell可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,比如普通用户使用sudo命令时就需要我们手动输入密码;expect就是能够完成这种自动交互任务,而无需人的干预。Expect的作者Don Libes在1990年开始编写Expect时对Expect做有如下定义:Expect是一个用来实现自动交互功能的软件套件(Expect [is a] software suite for automating interactive tools)。系统管理员可以使用它创建用来实现对命令或程序提供输入的脚本:一般来说这些输入都需要手工输入(比如之前提到的执行sudo程序期望用户从终端输入用户密码)进行的,Expect则可以根据程序的提示 模拟标准输入给程序提供信息来实现交互程序执行。甚至可以实现简单的BBS聊天机器人。

Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。

安装expect

1
yum install expect

会安装expect和tcl(expect依赖tcl)。

spawn

spawn [args] program [args]
创建一个新的进程运行program [args]。program的标准输入、标准输出和标准错误都连接到Expect,这样程序就可以被Expect读写了。当Expect执行close命令或者进程关闭了任何文件标识符时,连接会断开。
当以spawn启动一个进程时,进程描述符被赋给变量spawn_id,它表示了当前进程,spawn_id可被读写,以实现任务控制。

1
2
3
4
5
[root@pinpoint_server test]# expect 
expect1.1> spawn sleep 100
spawn sleep 100
31172
expect1.2>

此处可以看到spawn产生了一个新的进程号为31172的进程,而系统中也确实可以看到该进程。

1
2
3
[root@pinpoint_server ~]# ps -ef | grep 31172
root 31172 31160 0 16:33 pts/2 00:00:00 sleep 100
root 31248 31127 0 16:34 pts/1 00:00:00 grep 31172

expect

expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
等待,直到模式patn匹配到spawn打开的进程的输出,超过指定的时间,或遇到EOF。

如果模式关键字是eof,那么对应的执行体是处理遇到文件结尾的

如果模式关键字是timeout,那么对应的执行体是处理超时的,如果没有指明timeout,则默认不会做任何动作。默认的超时时间是10秒,但是可以通过如下命令重设
set timeout 30
如果设置为-1,则意味着无限等待。

如果模式关键字是default,那么对应的执行体会处理超时或文件结尾。

每次新的输出到达时,会按顺序比较每个模式,如果模式匹配到了,那么对应的执行体会被执行。如果有多个模式匹配,只执行第一个出现的。

下面的例子,看起来像一个可以成功登陆的脚本片段。

1
2
3
4
5
expect {
"yes/no" {send "yes\r";exp_continue}
"password:" {send "$passwd\r"}
default {exit}
}

字串中可以使用^匹配起始串,用$匹配结尾串。

如果模式关键词是null,那么仅当匹配到一个单个ascii字符”0”时才会执行后面的执行体。没有可用的办法来匹配0字节数据,不论是通过全匹配还是正则匹配。

在获得匹配后,程序的所有输出字串,截至匹配字串,被保存在变量expect_out(buffer)中,多至九个匹配子串分别被保存在变量expect_out(1,string)至expect_out(9,string)中。

选项 说明
-gl 保护以”-“开始的模式不被认为是选项
-re 正则表达式,在单项前面指定
-ex 匹配确切的字串,不对*、^和$等特殊字符进行翻译
-nocase 不区分大小写
-timeout 设置当前expect的超时时间,而不是使用变量timeout的时间

exp_continue

exp_continue [-continue_timer]
允许expect继续执行自身而不是往下执行,默认情况下,exp_continue会重置timeout,如果不想重置timeout,使用-continue_timer选项。
注意,exp_continue会重新执行本expect,请参考远程登录服务器实例

expect_user

expect_user [expect_args]
类似expect,不过是从标准输入读取字符,行必须以回车结尾,以使expect能识别它们。

send

send [-flags] string
发送string到当前进程,例如:

1
send "hello world\r"

就是发送h e l l o w o r l d 到当前进程,
字符会立即被发送,尽管那些行缓冲的程序只会在有回车键时才读取,回车键用’\r’表示。因此,下面的示例和上面示例等同。

1
2
send "hello "
send "world\r"
选项 说明
其后的参数被强制解释成字串,而不是选项。
-i 选项说明字串发送给spawn_id。
-s/-h 慢的输入\类人的输入方式,可以提供输入的间隔设置。具体参见man手册

send_error

send_error [-flags] string
类似send,不过输出发送到标准错误,而不是当前进程

send_log

send_log [--] string
类似send,不过string只发送到log文件。(see log_file)

send_tty

send_tty [-flags] string
类似send,不过输出发送到/dev/tty而不是当前进程。

send_user

send_user [-flags] string
类似send,不过输出发送到标准输出,而不是当前进程。

interact

interact [string1 body1] ... [stringn [bodyn]]
将当前进程的控制权交付给用户。
string-body对可以作为参数,以使当有string输入时,执行body。下面的示例看上去是一个可运行片段。

1
2
3
4
interact {
"abc" {send_user "you typed abc\n"}
"123" {send_user "you typed 123\n"}
}

输入的字符会按string列出的顺序进行匹配,当有部分匹配时,当前输入的字符不会被发送到当前进程,只有当后续输入的字符不能使之匹配时才会发送,若匹配则执行body。以上例为例,在输入”abc”的过程中,进程不会回显这些字符,如果输入”abq”,则只有在输入到”q”时,”abq”才会同时被回显。

选项 说明
-ex 防止以”-“开头的模式被翻译成选项
-re 用正则匹配的模式翻译string,此选项下匹配的子串被保存在变量interact_out中。类似expect的expect_out。
-echo 回显每一个字符,即使这个字符会被匹配中

当模式是eof时,表示遇到文件结尾的行为,默认是”return”。
当模式是timeout时(需指定超时时间),表示在指定时间没有输入时的行为,此项没有默认的超时时间,变量timeout的值在此无效。
当模式是null时,仅当匹配到一个单个ascii字符”0”时才会执行后面的执行体。没有可用的办法来匹配0字节数据,不论是通过全匹配和正则匹配。
interact中的return使interact返回到它的调用。而inter_return使interact的调用执行return。

sleep

sleep seconds
脚本进入睡眠模式,睡眠时间单位为秒

close

close [-slave] [-onexec 0|1] [-i spawn_id]
关闭连接到当前进程的连接

选项 说明
-i 指定关闭名为spawn_id的进程
-onexec 检测是否有新打开的进程或进程是否有重叠,若有,0:保持打开,1:强制关闭
-slave 关闭spawn_id关联的子进程

exit

Expec退出

远程登录服务器实例

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/expect 
set timeout 10
spawn ssh root@10.240.180.152
expect {
"password" {send "passwd\r"}
"yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir testExpect\r"}
expect eof
exit

参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/expect 
set ip [lindex $argv 0]
set password [lindex $argv 1]
set timeout 10
spawn ssh root@$ip
expect {
"password" {send "$password\r"}
"yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir test1\r"}
send "exit \r"
expect eof
exit

脚本执行过程如下:

1
2
3
4
5
6
7
8
9
[root@manage expect]# ./a.exp 10.240.180.152 passwd
spawn ssh root@10.240.180.152
root@10.240.180.152's password:
Last login: Sat Apr 28 10:50:49 2018 from 10.240.180.151
[root@lang_152 ~]# mkdir test1
[root@lang_152 ~]# exit
logout
Connection to 10.240.180.152 closed.
[root@manage expect]#

参考:
https://www.cnblogs.com/yinghao1991/p/6926125.html
https://blog.csdn.net/chinalinuxzend/article/details/1761202
https://www.jianshu.com/p/70556b1ce932