最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

Expect 学习笔记

来源:动视网 责编:小OO 时间:2025-10-01 18:40:50
文档

Expect 学习笔记

Expect学习笔记————Tim一、Expect介绍Expect是一种TCL扩展性的语言,主要用于完成系统交互方面的功能,比如SSH、FTP等,这些程序都需要手工与它们进行互动,而使用Expect就可以模拟人手工互动的过程,使用一种自动的方式控制。Expect中,有三个重要的主命令,分别是spawn、expect、exp_send,这三个命令几乎存在于所有Expect脚本中,除了这些之外,还有很多Expect所特有的参数、变量,它们也作用与Expect的方方面面。二、Expect命令Expe
推荐度:
导读Expect学习笔记————Tim一、Expect介绍Expect是一种TCL扩展性的语言,主要用于完成系统交互方面的功能,比如SSH、FTP等,这些程序都需要手工与它们进行互动,而使用Expect就可以模拟人手工互动的过程,使用一种自动的方式控制。Expect中,有三个重要的主命令,分别是spawn、expect、exp_send,这三个命令几乎存在于所有Expect脚本中,除了这些之外,还有很多Expect所特有的参数、变量,它们也作用与Expect的方方面面。二、Expect命令Expe
Expect学习笔记

————Tim

一、Expect介绍

Expect是一种TCL扩展性的语言,主要用于完成系统交互方面的功能,比如SSH、FTP等,这些程序都需要手工与它们进行互动,而使用Expect就可以模拟人手工互动的过程,使用一种自动的方式控制。

Expect中,有三个重要的主命令,分别是spawn、expect、exp_send,这三个命令几乎存在于所有Expect脚本中,除了这些之外,还有很多Expect所特有的参数、变量,它们也作用与Expect的方方面面。

二、Expect命令

Expect中命令是最重要的部分,它们完成Expect中最关键的功能,命令使用的特点就是他们本身就可以单独执行,使用上类似于:

命令 [选项] 参数

spawn

spawn命令是Expect的初始命令,它用于启动一个进程,之后所有expect操作都在这个进程中进行,如果没有spawn语句,整个expect就无法再进行下去了,使用方法就像下面这样:

    spawn  ssh  root@192.168.0.1

在spawn命令后面,直接加上要启动的进程等信息

当然,如果真的不要spawn过程也没有关系,虽然这样就没有办法单独执行,但是这个脚本可以与任何调用它的进程进行交互。

除此之外,spawn还支持其他选项:

-open        启动文件进程,具体说明请参照下面的选项部分。

-ignore    忽略某些信号,具体说明请参照下面的选项部分。

expect

使用方法:

expect  表达式  动作  表达式  动作 ………………

expect命令用于等候一个相匹配的输出,一旦匹配就执行后面的动作,这个命令接受几个特有参数,用的最多的就是-re,表示使用正则表达式的方式匹配,使用起来就像这样:

    spawn  ssh  root@192.168.0.1

    expect  “password:”  {exp_send  “word\\r”}

从上面的例子可以看出,expect是依附与spawn命令的,当执行ssh命令后,expect就匹配命令执行后的关键字:password:,如果匹配到关键字就会执行后面包含在{}括号中的exp_send动作,匹配以及动作可以放在二行,这样就不需要使用{}括号了,就像下面这样,实际完成的功能与上面是一样的:

spawn  ssh  root@192.168.0.1

    expect  “password:”

exp_send  “word\\r”

expect命令还有一种用法,它可以在一个expect匹配中同时匹配多个关键字,只需要将关键字放在一个大括号中就可以了:

    spawn  ssh  root@192.168.0.1

    expect  {

        -re  “password:”  {exp_send  “word\\r”}

        -re  “TopsecOS#”  { }

}

上面的例子中,在一个expect匹配中可以匹配二个不同情况,如果发现有password:字符就执行后面的动作,而发现的是另外一个TopsecOS#时,因为后面的动作为空,就会退出这个expect动作,在这些动作中也有很多参数,我们在后面来慢慢介绍。

上面我们看到了一种【表达式-动作】模式,还有人喜欢使用另一种格式,就像下面这样:

    spawn ssh root@192.168.0.1

    expect –re “password:” {

        exp_send “word\\r”

} –re “TopsecOS#” {

    

}

这种格式的好处是减少了一次缩进,不过看起来就没有那么清晰了,喜欢哪一种可以自己决定。

expect_before

在这个语句以下的所有expect语句之前,首先做一次匹配,使用这个命令需要小心,首先来看例子:

    expect {

        eof        eofproc

        “login:”        {send “$user\\r”}

}

expect {

    eof        eofproc

    “password”    {send “$password\\r”}

}

expect {

    eof        eofproc

    “$prompt”        {send “$cmd\\r”}

}

上面的例子中,每一个expect都有一个eof过程,而且都是首先检查有没有eof事件,然后再往下检查其他的事件。对于这种情况,我们可以用下面的语句来代替:

    expect_before  eof  eofproc

    expect “login:”        {send “$user\\r”}

    expect “password”    {send “$password\\r”}

    expect “$prompt”        {send “$cmd\\r”}

expect_after

与上面的expect_before一样,不过expect_after是用于在expect语句后面做匹配的,比如下面的语句:

    expect {

        “login:”        {send “$user\\r”}

eof        eofproc

}

expect {

    “password”    {send “$password\\r”}

    eof        eofproc

}

expect {

    “$prompt”        {send “$cmd\\r”}

    eof        eofproc

}

        就可以用这样的语句来代替:

    expect_after  eof  eofproc

    expect “login:”        {send “$user\\r”}

    expect “password”    {send “$password\\r”}

    expect “$prompt”        {send “$cmd\\r”}

看起来简单,但是还有一些需要注意的地方:

1.expect_after或者前面说的expect_before语句,都是对这个命令下面的expect语句产生影响的,比如上面的命令中如果把expect_after放在最后一条,那么中间的login之类的expect都是无效的

2.expect_after是在expect命令之后做匹配,所以如果有一个expect eof这样的命令,则expect_after eof是无效的,因为先匹配了expect命令中的eof,比如下面的语句:

expect_after eof {puts “it’s after”}

expect eof {puts “it’s expect”}

则只能匹配到expect中的那个eof,输出是it’s expect,为什么呢?因为上面的语句代表这样的意思:

expect {

        eof        {puts “it’s expect”}

        eof        {puts “it’s after”}

}

这样应该很明显了。

3.如果有多个expect_after或者expect_before命令,则第二个命令会替换第一个,比如下面的情况:

expect_after “pat1”    act1

expect p1

expect p2

expect_after “pat2” act2 “pat3” act3

expect p3

这种情况下,程序流程首先匹配 p1 pat1 p2 pat1,然后因为第二个expect_after语句的关系,下面的匹配则是p3 pat2 pat3,这里第二个after语句替换了第一个,p3不再匹配pat1了。

4.

expect_user

expect_user命令用来匹配从用户那里的输入,之前进行匹配时,都是使用expect命令来匹配程序的输出,但这个命令则可以根据用户的输入进行匹配并完成相应的动作,看看下面的语句:

    expect_user {

        -re hello

        {puts stdout "HELLO"; exp_continue}

}

这个代码段首先从用户那里获取输入,如果发现输入的是hello字符时,就会激活下面的动作,打印HELLO字符,因为exp_continue命令的存在,这个程序会一直循环下去。

exp_send

在上面的介绍中,我们已经看到了exp_send命令的使用,exp_send命令是expect中的动作命令,它还有一个完成同样工作的同胞:send,exp_send命令可以发送一些特殊符号,我们看到了\\r(回车),还有一些其他的比如:\\n(换行)、\(制表符)等等,这些都与TCL中的特殊符号相同。

send命令有几个可用的参数:

-i    指定spawn_id,这个参数用来向不同spawn_id的进程发送命令,是进行多程序控制的关键参数。

-s    s代表slowly,也就是控制发送的速度,这个参数使用的时候要与expect中的变量send_slow相关联

exp_continue

这个命令一般用在动作中,它被使用的条件比较苛刻,看看下面的例子:

    spawn  ssh  root@192.168.0.1

    expect  {

        -timeout  60

        -re  “password:”  {exp_send  “word\\r”; exp_continue}

        -re  “TopsecOS#”  { }

        timeout  {puts  “Expect was timeout”; return}

}

在这个例子中,可以发现exp_continue命令的使用方法,首先它要处于一个expect命令中,然后它属于一种动作命令,完成的工作就是从头开始遍历,也就是说如果没有这个命令,匹配第一个关键字以后就会继续匹配第二个关键字,但有了这个命令后,匹配第一个关键字以后,第二次匹配仍然从第一个关键字开始。

exp_internal

这是一条用来打开Expect调试模式的命令,它可以将整个匹配,操作过程中间发生的事情显示出来,它接受的值包括:非0值(打开调试模式)、0(关闭调试模式)、-f file(将调试内容写入文件),使用起来就像下面这样:

spawn ftp 10.11.105.15

exp_internal 1

expect {

        -re "Name" {exp_send "ftp\\r"; exp_continue}

        -re "Pass" {exp_send "ftp\\r"; exp_continue}

-re "ftp>" {exp_send "by\\r"}

}

expect EOF

注意上面的exp_internal是放在spawn命令后面的。

如果想将调试信息放入文件,则修改exp_internal 1语句,变成下面的样子:

exp_internal -f output.txt 1

具体的组合如下:

exp_internal    0        关闭诊断模式。

exp_internal    1        开启诊断模式,诊断信息输出到标准输出中。

exp_internal –f file 0    开启诊断模式,诊断信息放入文件但不输出到标准输出。

exp_internal –f file 1    开启诊断模式,诊断信息不光写入文件,也输出到标准输出。

exp_pid

exp_pid命令用来获取当前spawn的进程号,他支持-i选项,用来指定具体的spawn进程,使用方法就像像下面这样:

    set pid [exp_pid –i $spawn_id]

send_user

send_user命令用来把后面的参数输出到标准输出中去,默认的send、exp_send命令都是将参数输出到程序中去的,用起来就像这样:

    send_user  “Please input passwd:”

这个语句就可以在标准输出中打印Please input passwd:字符了。

send_error

send_error命令用来把后面的参数输出到标准错误输出中去,相比send_user来说它们的最终去向不同。

send_log

send_log命令用来把后面的内容输出到log记录文件中去,这个命令需要与log_file命令配合使用,如果log_file命令指定了一个文件记录程序输出,那么send_log命令则可以自己在文件中添加一些内容。如果没有log_file,那么send_log是无效的,里面的内容不会被打印到标准输出中去。

close

close命令用来关闭连接,它支持-i选项,用来关闭指定句柄的过程,如果不带任何选项,则关闭当前$spawn_id变量指定的过程。

wait

wait命令与close命令相似,都是用来关闭连接的,但wait命令是等待系统主动返回eof,也就是结束信号后才关闭,而不是像close那样无条件关闭,与close命令一样,它也支持-i选项。

log_user

log_user命令用来指定Expect输出的位置,默认情况下log_user的值是1,表示所有输出都放在标准输出中(一般是显示器,如果采用重定向也可以定位到文件中),如果将这个值赋值为0就表示不需要任何输出。

log_file

log_file命令用来将输出记录到一个文件中去,使用格式如下:

log_file <选项> 文件名

选项包括下面几种:

-open    当一个文件已经被打开时,使用这个选项可以往这个文件里继续添加内容,这个选项一般用于windows系统,因为windows系统中文件是独占的,Linux系统中就不存在这个问题了。

-leaveopen    当文件已经被打开时,不继续向这个文件中添加内容。

-noappend    如果文件已存在,那么清空文件中的内容,然后再写入。

-a    如果文件已存在,则将新内容附加到当前文件的末尾。

当这个命令带有文件名时,表示将spawn程序的标准输出记录到文件中,只使用log_file命令但不带文件名则表示关闭记录,这样你就可以只记录自己感兴趣的部分了。

remove_nulls

expect中,默认会去掉返回中的控制符,注意:不要将控制符与空白符搞混,控制符主要是信号,他们是不会显示在屏幕上的,比如使用ctrl+c(中断)、ctrl+d(终止)等组合键发送的信号,信号是ASCII码表中01-31区间中的字符;空白符则是可以显示出来的空白符号,比如空格、回车、换行、制表等一系列符号。

remove_nulls命令可以控制是否去掉返回中的控制符,使用方法如下:

    remove_nulls        0        # 关闭自动去掉控制符的功能

    remove_nulls        1        # 开始知道去掉控制符的功能,这是默认值。

match_max

match_max用来设置expect_out(buffer)变量的缓存大小,默认情况下expect_out(buffer)变量缓存是2000个字符,你可以设置这个大小来设置更符合实际的缓存,使用方法如下:

match_max        65535

这是一般的方法,它还支持2个选项,其中-d表示设置为默认值,也就是default的意思,-i则表示设置某个spawn_id指定的进程缓存,使用方法如下:

    match_max        -d  1048576

    match_max        -i    lo_spawnid

使用-d选项一般是用于多个spawn进程同时运行的情况。

trap

在脚本运行期间监听系统信号,系统信号支持3种方式:C语言方式,比如SIGINT、SIGHUP等;简略方式,比如-INT、-SIGHUP;Linux系统的kill模式,比如9、15等;监听到这些信号之后就执行规定的动作。使用方法就象这样:

    trap {

        send_user “bye bye\\r”

        exit

    } SIGINT

上面的脚本中,在脚本运行期间一旦用户发送了SIGINT信号(一般都是ctrl+c),就会打印bye bye字符,然后退出脚本。信号的具体表格请查阅其他资料,而且建议使用C语言方式,因为这是目前使用最广泛的,比较容易做代码移植。

如果希望一次性监听多个信号,则可以把信号一次性的赋给trap命令,比如:

    trap intproc {SIGINT SIGHUP SIGQUIT}

trap支持2个选项:-name –number,分别返回监听到的信号名称及数值,使用方法如下:

    trap {

        send_user “signal name is [trap –name]”

        send_user “signal integer is [trap –number]”

        exit

} {SIGINT SIGHUP SIGQUIT}

        

        如果希望取消某些信号,则有一个特殊关键字SIG_IGN来做这件是,比如:

            trap SIG_IGN { SIGINT SIGHUP SIGQUIT }

        这样在脚本运行期间即使用户输入这些信号也会被忽略掉。

有时候希望在脚本前半部分忽略信号,但后半部分不忽略,使用特殊关键字SIG_DFL可以恢复这些信号的功能:

    trap SIG_IGN { SIGINT SIGHUP SIGQUIT }    # 取消这些信号功能

    ..................

    trap SIG_DFL { SIGINT SIGHUP SIGQUIT }    # 恢复这些信号功能

exit

exit命令功能很简单,就是直接退出脚本,但是你可以利用这个命令对脚本做一些扫尾工作,比如下面这样:

    exit –onexit {

        exec rm $tmpfile

        send_user “Good bye\\n”

}

interact

interact命令是Expect中非常重要的命令之一,它用来在脚本中将控制权交给用户,由用户与spawn生成的进程进行交互,比如登录ftp服务器并下载的过程中,登录ftp服务器的过程可以由用户输入自己的用户名和密码,然后用户再输入q字符将控制权交给脚本,由脚本完成后面的交互动作,这个功能的实现代码如下:

    spawn ftp 172.18.1.111

interact {

        "d"     {puts [exec date]}

        "q"     {return}

}

send "\\r"

expect {

ftp> {send "by\\r"}

}

expect {

        default {}

}

上面的代码每一行的含义如下:

第一行的spawn命令用来打开一个ftp登录过程,需要特别注意的是,interact命令是不能单独运行的,他必须在一个spawn过程中才能生效,这一点与expect命令是相同的,interact命令也支持-i参数用来做多进程控制,但目前我们不涉及这部分。

    第二行的interact命令使用起来就和expect命令完全一样了,当用户输入d这个字符时,执行date命令,这里只是为了演示一下,没有实际意义,输入q字符就退出interact交互过程,这个退出交互过程是由return命令实现的,这也是要特别注意的,如果没有这个命令,则后续的命令都无法生效了,因为你没有将控制权从用户那里转交回脚本;除了d、q这2个字符,用户输入的任何其他字符都会被忠实的交给ftp过程,所以用户可以在这里进行ftp用户登录以及其他操作,操作完了按一下q就可以了,真实代码中当然不能用q来做这样的动作,因为如果用户的用户名中如果正好有这个字符可就出问题了,你可以找一个不常用的字符来代替,还需要注意一点:interact并不是只能监听一个字符,你尽可以用一个字符串来代表交互完成,比如OK或者I’m Finished\\r等,像这样的监听字符是不会显示在屏幕上面的。

    下面的send \\r命令很重要,因为下面的expect命令需要匹配ftp>字符,但interact过程将权限交给脚本的时候,expect的buffer是空的,必须使用send命令将程序的提示符推到buffer里面去,后面的过程就没什么重要的部分了。

interact命令也支持-gl、-ex、-re选项,而且与expect命令相似,它也有interact_out这个专用数组来做其他操作,数组包括interact_out(X,string),其中X是0-9之间的数字,比如interact_out(0,string)表示所有匹配到的字符等;但是注意interact_out这个数组中不包含interact_out(buffer)这个变量,它是与expect_out(buffer)这个变量合并的。想看更具体的内容,请参阅第三部分。

interpreter

interpreter用于在expect过程中调用解释器,执行到这一句之后,expect会暂时停下来显示解释器,可以在解释器中调试脚本中的内容,比如puts内部变量的值或者其他操作,到最后使用return命令就可以让脚本继续向下执行了。

调用解释器之后,可以使用更多的interpreter命令调用多个解释器,最后还是用return返回。

三、Expect选项

-i选项

-i选项一般用于同时控制多个spawn进程,通过这个选项向不同spawn_id发送命令就可以同时控制多个进程了,它有二种使用方法:

1.直接使用:

    spawn ftp connect

    set spid_ftp $spawn_id

    spawn telnet connect

    set spid_telnet $spawn_id

    exp_send –i $spid_ftp “ftp command”

    exp_send –i $spid_telnet “telnet command”

2.expect过程调用

    expect {

-i $spid

-timeout "your timeout"

-re ".+" {append result $expect_out(buffer)}

-re "other exit condition" {}

}

-d选项

几乎所有expect命令都支持-d选项,d代表default,也就是设置某些命令的默认值,比如:

remove_nulls –d 1

timeout –d 30

-f选项

f代表 file ,某些Expect命令支持这个选项,比如exp_internal命令,这个选项用来将某些命令的输出放入一个文件中去,因此这个选项后面必须带有文件的路径作为值。

-s选项

s代表slowly,也就是发送速度,这个选项一般用于send族命令中,比如send、exp_send、send_user、send_error等等,使用这个选项之前,必须先对send_slow变量赋值,这个变量赋值方法的说明请查看第三部分。

-h选项

h代表human,用来模拟人敲击键盘的动作,可以用于所有send命令族,比如exp_send、send_user等等,使用之前必须先对send_human变量赋值,这个变量的详细说明请查看第三部分。

-gl选项

gl代表global,用于指定全局类型的表达式,用于expect、interact命令族中,比如expect、expect_user、expect_error等,global形式的表达式与DOS的通配符相似,用*表示任意多个任意字符,?表示一个任意字符,这是一种很简单的表达式。

-ex选项

ex代表exact,用于指定精确类型的表达式,用于expect、interact命令族中,这种类型的表达式特点就是所有字符都精确代表它本身的含义,不存在通配符。

-re选项

re代表regular expression,也就是正则表达式,用于expect、interact命令族中,这种类型的表达式具有匹配一切字符的能力,但掌握起来比较困难,是程序员中的一个难点,如果有兴趣了解这个强大工具,请参考我的另一个文档《正则表达式参考》。

-null选项

这个选项可以用于expect和send过程,用于expect时表示连同空字符也一样做匹配(默认情况下不匹配),用于send则用于发送空字符,在send命令中还可以一次发送多个空字符,比如:send –null 3

-open选项

这个选项用于spawn命令,默认情况下spawn是开启一个新进程,这个进程一般都是程序,使用-open则开启新文件,使得spawn不光可以用于进程交互,还可以与文件交互,这个选项后面的参数必须是一个文件句柄,就象这样:

    set file [open /etc/resolv.conf r]

    spawn –open $file

    expect nameserver {puts “it’s name server address”}

-name选项

这个选项用于trap命令,用来返回信号的名称,具体使用方法请看trap命令部分。

-number选项

这个选项也用于trap命令,用来返回信号代号(整数值),具体使用方法请看trap命令部分。

-ignore选项

这个选项用于spawn命令,用来忽略某些特殊的信号,比如:

    spawn –ignore SIGINT –ignore SIGHUP ping $host

这样在spawn执行期间用户如果发送SIGINT(ctrl+c)和SIGHUP(ctrl+d)信号,系统不会理会。

-onexit选项

用于exit命令,用来在脚本退出之前做一些扫尾性的工作,具体使用方法请查看exit命令说明。

-indices选项

这个选项用于expect命令中,用来开启expect的子模式匹配方式,包括expect_out(x,string) expect_out(x,start) expect_out(x,end)这3个变量族,这个选项默认情况下已经是开启的,所以一般很少使用。

-info选项

用于expect_before和expect_after命令中,用来显示这2个命令实际匹配的情况,一般都是用于调试,比如下面的情况:

spawn ping 172.18.1.111 -c 2

expect_after {

        time {puts time}

}

expect {

        eof {puts "yes"}

}

puts [expect_after -info]

代码的输出如下:

spawn ping 172.18.1.111 -c 2

PING 172.18.1.111 (172.18.1.111) 56(84) bytes of data.

 bytes from 172.18.1.111: icmp_seq=0 ttl= time=0.263 ms

time

-gl time {puts time}

除此之外,还可以使用-all参数来获取所有spawn_id上的内容,比如:

    expect_after –info –all

--选项

这是终止选项,也就是表示在这个选项之后没有其他选项了,比如下面的命令:

    send –s            # -s是一个选项

    send -- -s        # -s是一个字符串,表示发送-s字符

建议在所有send和expect中都使用这个选项,这样比较安全,比如:

    send $unknow        # 比较危险

    send -- $unknow    # 安全

上面为什么会比较危险呢?因为如果$unknow这个变量里面的值如果是以-开头,比如-know,send就会当作是一个-know选项,就会报错没有这个选项了。

四、Expect变量

expect中有很多有用的变量,它们的使用方法与TCL语言中的变量相同,比如:

set        变量名    变量值            # 设置变量的方法

puts    $变量名                    # 读取变量的方法

expect_out数组

expect_out数组专用与expect命令,里面的元素包括:

    expect_out(buffer)

    expect_out(X,string)

    expect_out(X,start)

    expect_out(X,end)

    expect_out(spawn_id)

上面的X字符表示从0-9的整数,具体使用下面将详细讲解。

expect_out(buffer)是一个看起来比较特殊的变量,刚刚看到的时候,会认为这是一个过程,然后用括号调用,但实际上在TCL语言中,括号也仅仅是一个字符而已,没有什么其他的意思,因此这整个字符串就是一个变量,这个变量中放置从上一次匹配到这一次匹配之间的所有返回,包括匹配字符本身。

比如你首先expect -re name,然后匹配到name之后就发送命令,再匹配expect -re password,那么expect_out(buffer)这个变量中就包括从name这个字符一直到password这个字符之间的所有设备输出。

除此之外,expect_out变量还有几个变形:

expect_out(x,string)

expect_out(x,start|end)

如果expect匹配是采用高级正则表达式的话(-re参数表示高级正则表达式方式匹配),那么每个子模式都有一个序号,序号从1-9,如:

set output "abbbcabkkkka"

expect -indices -re "b(b*).*(k+)"  $output

那么:

set expect_out(0,start)         ==>        1

set expect_out(0,end)          ==>        10

set expect_out(0,string)      ==>        bbbcabkkkk

set expect_out(1,start)      ==>        2

set expect_out(1,end)          ==>        3

set expect_out(1,string)      ==>        bb

set expect_out(2,start)      ==>        10

set expect_out(2,end)          ==>        10

set expect_out(2,string)      ==>        k

set expect_out(buffer)          ==>        abbbcabkkkk

上面的匹配中,参数-indices表示以列表方式匹配。0代表匹配到的所有字符,相当于match,1-9则是子模式的编号。

expect_out(x,string)这样的值都不同的含义,列表如下:

expect_out(buffer)

放置上一条expect语句匹配到这一次expect语句匹配之间的所有标准输出中的内容,所以这个变量中的值是随时会改变的。

        expect_out(x,string)

x是一个数字,从0到9,加起来一共是10个变量,其中0变量比较特殊,它里面保存整个expect正则表达式中匹配到的所有内容,而从1开始到9的变量中,保存正则表达式中子模式的值,也就是括在()中的那部分内容,从左往右计算。

        expect_out(x,start)

x与上面的含义相同,这个变量的值是一个数值,以expect_out(1,start)为例,它里面保存第一个子模式在expect_out(buffer)变量中匹配到的时候的索引值,比如第一个子模式匹配是在expect_out(buffer)中的第10个字符开始匹配到的,那么这个变量的值就是10.

        expect_out(x,end)

这个与上面的含义是相同的,只不过这里保存的是在expect_out(buffer)中匹配到的结尾的索引值。

expect_out(spawn_id)

这是一个用于多线程管理时的变量,一般与any_spawn_id一起使用,我们来设想下面这一种情况:

如果希望在进程1上匹配字符串A,进程2上匹配字符串B,在进程1、2上都匹配字符串C,我们可以用下面的方式(注意这只是伪代码):

spawn porcess1

set one_spawnid $spawn_id

spawn process2

set two_spawnid $spawn_id

expect {

    -i $one_spawnid A    执行动作

    -i $two_spawnid B    执行动作

    -i $any_spawn_id C    执行动作

}

好了,现在可以达到上面的要求了,但是问题是如果某一次我们匹配到了C这个字符,那么怎么判断这个字符是在哪个进程上出现的呢?这是就可以读取expect_out(spawn_id)这个变量,来获取那个出现这个字符的进程了。

interact_out数组

这个数组专用用interact命令中,数组元素包括:

    interact_out(X,string)

    interact_out(X,start)

    interact_out(X,end)

具体使用方法与expect_out这个数组完全一样,但是注意这个数组中可没有interact_out(buffer)这个元素,这个元素与expect_out(buffer)是合并的,也就是说interact命令中的输出也是放在expect_out(buffer)这个数组元素中的。

send_slow

这个变量设置expect发送字符的速度,它接受2个参数,一个是字符个数,另一个是时间,使用方法如下:

set  send_slow  {character seconds}

第一个参数是字符个数,第二个是时间,单位为秒,比如:

set  send_slow  {10 1}

表示每秒钟发送10个字符,当然如果生效,send命令必须加上-s参数,比如:

set  send_slow  {10 1}

send  -s  “hello\\r”

send_human

这是一个模拟人用手打字的模式,其实也就是每个字母在输出时都有一些延时,好像人在用键盘打字一样,这个变量需要5个参数,并且必须与send这一类命令加上-h参数才能使用,变量赋值方法如下:

    set send_human {.1 .3 1 .05 2}

上面的5个值中,每个值的含义如下:

    .1        每个字母输出的平均时间

    .3        一个单词完成后,停留的平均时间

1        是做时间检查的间隔时间,与后面2个参数一起发生作用,也可以理解为刷新率,.1的话比较快,1就稍微慢一点但是比较合理,10的话就没什么变化了……,比如上面后面2个参数设定为0.05-2秒,那么实际输出就在0.05-2秒之间上下浮动,那么如果设置为10,这个浮动就很稳定,就一直是2秒的间隔。

            .05        2个字符之间输出的最小间隔

            2        2个字符之间输出的最大间隔

        实际使用时,就象下面这样:

            set send_human {.1 .5 1 .05 1}

send -h "I'm hungry. Let's do lunch.\\n"

spawn_id

spawn_id也是expect中的重要变量,它专门用于expect中的进程管理,当在expect中需要执行多个进程时,那个进程的id号码就会被设置为spawn_id,如果希望在二个进程之间切换,只需要设置这个变量就可以了,看看例子:

    spawn  ssh  root@192.168.0.1

    set  ssh_id  $spawn_id

    send  “word\\r”

    spawn  telnet  192.168.0.1

    set  telnet_id  $spawn_id

    send  “word2\\r”

    set  spawn_id  $ssh_id

    send  “word\\r”

    set  spawn_id  $telent_id

    send  “word2\\r”

在上面的例子中,启动了二个进程,并且交错的在二个进程间发送字符串。

any_spawn_id

这是一个用于多进程的变量,表示在所有的进程上做操作,比如我希望在进程1上期待A,进程2上期待B,但在2个进程中都要期待C,就可以用下面的方式:

expect {

    -i $process1 A

    -i $process2 B

    -i “$process1 $process2” C

    -i $any_spawn_id C        # 这一句和上面一句的作用是相同的

}

user_spawn_id

这是获取标准输入和标准输出spawn_id的方法,实际上可以用expect_user来代替,下面的2段代码的功能是相同的:

set spawn_id $user_spawn_id

expect “hello” {puts “hello world”}

expect_user “hello” {puts “hello world”}

error_spawn_id

从标准错误中获取spawn_id

tty_spawn_id

从重定向的管道获取spwan_id

五、Expect特殊关键字

expect中的特殊关键字用于匹配过程,代表某些特殊含义或状态,一般用于expect族命令中而不能在外面单独使用,也可以理解为事件,使用上类似于:

expect eof {} full_buffer    {}

eof

eof(end-of-file)关键字用于匹配结束符,比如文件的结束符、FTP传输停止等情况,在这个关键字后跟上动作来做进一步的控制,特别是FTP交互操作方面,它的作用很大。用一个例子来看看:

    spawn ftp  anonymous@10.11.105.110

    expect {

        “password:”  {exp_send  “who I’m I”}

        eof  {ftp connect  close}

}

interact {}

full_buffer

full_buffer用于expect_user过程中,当用户向标准输入输入字符时,通过expect_user命令可以匹配用户的输入,但是匹配到最后时,用户的某些输入可能并没有被匹配到,但有不希望丢弃这部分字符,就可以采用匹配full_buffer的方式,将用户输入放入expect_out(buffer)变量中去,例如下面的例子:

set timeout 3

while 1 {

        expect_user {

                eof exit

                timeout {

                        expect_user "*"

                        send $expect_out(buffer)

                }

                full_buffer {send $expect_out(buffer)}

        }

}

timeout

timeout是expect中的一个重要变量,它是一个全局性的时间控制开关,你可以通过为这个变量赋值来规定整个expect操作的时间,注意这个变量是服务与expect全局的,它不会纠缠于某一条命令,即使命令没有任何错误,到时间仍然会激活这个变量,但这个时间到达以后除了激活一个开关之外不会做其他的事情,如何处理是脚本编写人员的事情,看看它的实际使用方法:

    set timeout 60

    spawn  ssh  root@10.11.105.110

    expect  “password:”  {send  “word\\r”}

    expect  timeout  {puts  “Expect was timeout”; return}

上面的处理中,首先将timeout变量设置为60秒,当出现问题的时候程序可能会停止下来,只要到60秒,就会激活下面的timeout动作,这里我打印一个信息并且停止了脚本的运行——你可以做更多其他的事情,看自己的意思。

在另一种expect格式中,我们还有一种设置timeout变量的方法,看看下面的例子:

    spawn  ssh  root@192.168.0.1

    expect  {

        -timeout  60

        -re  “password:”  {exp_send  “word\\r”}

        -re  “TopsecOS#”  { }

        timeout  {puts  “Expect was timeout”; return}

}

在expect命令中间加上一个小横杠,也可以设置timeout变量

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

default

default包括timeout和eof这2个事件,当这2个时间中的任何一个发生时,都可以激活default事件。

*

一个简单的星号符,表示任何字符,一般在expect中用来清除expect中的核心缓存(或者说清空expect_out(buffer)变量)。

null

如果使用remove_nulls命令关闭了去掉返回中控制符的功能后,就可以使用null这个关键字来匹配这些控制符。

expect命令参数

Linux中,如果在脚本第一行加上 #!/usr/bin/expect 语句,则可以在为脚本赋与运行权限后,直接采用 ./script这样的方式来运行脚本,但expect本身还支持一系列参数用于不同的情况,比如 #!/usr/bin/expect -– 表示脚本后面没有其他参数了,这些参数列表如下:

-    从标准输入获取命令。

--            终止符号,表示后面没有其他参数。

-b            一次一行的读取脚本,这一般用于没有文件缓冲的情况。

-c cmd        先执行-c后面的命令,然后再执行脚本中的内容,比如:

                #!/usr/bin/expect –c puts jxc

                puts “hello world”

            上面的语句先打印jxc,然后打印hello world

-f file    从指定的文件中读取内容,这样就可以将expect命令和执行文件分开了。

-d        显示expect运行前的交互信息,比如argc argv0 argv等参数的值,运行的命令等。

-D 1/0    开启或关闭调试模式。

-i    交互式运行,就像直接在命令行上用expect命令进入控制台一样。

六、Expect实例与技巧

    1、技巧汇总:

在Expect中产生坚持——每隔一段时间进行一次检查

在Expect中进行坚持比较简单,只需要写一个循环就可以了,看看下面的代码:

    for {} {1} {} {

        spawn  ssh  root@192.168.0.1

        expect  {

            -timeout  60

            -re “password:”  {exp_send  “word\\r”; exp_continue}

            -re  “TopsecOS#”  { }

            timeout  {puts  “Expect was timeout” }

}

}

因为for循环的判断一直都是1(正确),所以这个循环会无的进行下去,可以通过在for循环中加上after命令控制检查的间隔时间。

一个Expect综合性的脚本,登陆设备,然后发送命令,最后获取命令的返回

#!/usr/bin/tclsh8.4

package require Expect

set i 1

set estr [ list "\\<" "\\>" "\\%" "\\&" "\\\\" ]

set cmds ""

set patten "error.*invalid input, parse error."

set user "superman"

set passwd "talent"

# 整个登陆设备的过程,这是一个很典型的Expect交互过程

spawn telnet 10.11.105.113 2002

expect OK { exp_send "\\r"; exp_continue }

expect {

        -timeout 10

        -re login: { exp_send  "$user\\r" ; exp_continue }

        -re Password: { exp_send "$passwd\\r"; exp_continue }

        -re TopsecOS# { }

        timeout {puts "The Expect is timeout"; return}

}

# foreach循环会遍历列表,将每个列表值进行循环

foreach str $estr {

        expect TopsecOS#

        exp_send "define service add name $str protocol 17 port 90\\r"

        puts $expect_out(buffer)

        incr i

}

Expect的-i参数及其他动作:

set spid [fw getspawnid]

exp_send -i $spid "system tcpdump ...."

set result ""

expect {

-i $spid

-timeout "your timeout"

-re ".+" {append result $expect_out(buffer)}

-re "other exit condition" {}

}

FTP、Telnet等程序的匹配及返回:

像FTP、Telnet等程序使用Expect时有一些比较特殊的地方:

1.使用正则表达式的方式匹配输出:

expect –re “Login:”

上面的语句中那个-re是非常重要的,表明这里的匹配采用正则表达式,如果没有这个参数,很可能匹配不成功。

2.登录过程中一定要加上exp_continue

expect –re “Password:”    {exp_send “passwd\\r”; exp_continue}

上面的语句中,那个循环匹配的exp_continue命令是非常必要的,在login、password过程中,都要有这个命令,否则很难进入命令行。

3.如果有其他命令输入,注意匹配空行时加上一个回车

expect –re “ftp>”        {exp_send “\\r”}

一般登录到远程设备之后,需要由用户输入一些命令,这些命令可能会放在其他过程中,为了完成整个登录过程,最终匹配到命令提示符之后,一定要加上一个回车,这样才能往下匹配。

4.最后一定要匹配eof字符

FTP、Telnet这一类应用完成时,一定要匹配eof连接终止信号,虽然eof一般表示文件终止,但部分网络连接也可以使用它。

下面是一个标准telnet过程的整体过程:

proc CheckError {info} {

        switch -regexp -- $info {

                "Connection closed"     {exit 1005}

                "No route to host"      {exit 1005}

                "Connection refused"    {exit 1005}

        }

}

spawn telnet $sys(host) $sys(port)

set timeout $sys(time)

set sys(prompt) "$sys(user).*\\[\\#\\$\\]"

expect {

        -re "login: $"      {exp_send "$sys(user)\\r"; exp_continue}

        -re "Password: $"   {exp_send "$sys(passwd)\\r"; exp_continue}

        -re "$sys(prompt)" {exp_send "\\r"}

}

CheckError $expect_out(buffer)

foreach tmp_cmd $sys(cmds) {

        expect -re $sys(prompt)

        exp_send "$tmp_cmd\\r"

}

expect -re "$sys(prompt)" {exp_send "exit\\r"}

expect eof

注意上面登录过程中的关键字匹配都采用了严格匹配方式,这种方式能比较好的避免在操作过程中其他关键字的干扰。

5.错误检查:

如果上面的操作中出现错误,我们就要进入错误检查阶段,这种错误有可能是密码不正确、主机不可达、连接主动断开等,这些错误情况在上面的CheckError过程中都有体现,将expect_out(buffer)参数的值传入就可以检查了(expect_out(buffer)参数的含义见签名的命令参数部分),如果不做检查,很容易出现程序异常退出的情况。

获取异步执行的输出

如果希望用异步执行的方式进行tcpdump操作,然后获取输出时,可以采用下面的方式:

        spawn ssh $root@172.22.1.100

        set ssh_spawnid         $spawn_id

        match_max -i $ssh_spawnid $exp(buffersize)

        expect {

                -re "yes/no"   {exp_send -i $ssh_spawnid "yes\\r"; exp_continue}

                -re "password: $" {exp_send -i $ssh_spawnid "$pc(password)\\r"; exp_continue}

                -re "$pc(prompt)"       {exp_send -i $ssh_spawnid "\\r"}

        }       

        expect {

                -re "password: $"       {exp_send -i $ssh_spawnid "$pc(password)\\r"}

                -re "$pc(prompt)"       {exp_send -i $ssh_spawnid "\\r"}

        }       

        set timeout $time

        set output ""

        

        expect {

                -timeout                $time   

                -re "$pc(prompt)"       {exp_send -i $ssh_spawnid "tcpdump $str &\\r"}

        }       

        expect timeout {

                append output $expect_out(buffer)

                exp_send -i $ssh_spawnid "fg\\r"

                after 500

                exp_send -i $ssh_spawnid "\\003"

                expect {

                        -re ".+$" {append output $expect_out(buffer); exp_continue}

                        eof {}  

                }       

        }       

                        

        #---------------------------Logout node device-------------------------

        expect -re "$pc(prompt)"        {       

                exp_send -i $ssh_spawnid "exit\\r"

                append output $expect_out(buffer)

        }       

        puts "expect output is:\\n---start---\\n$output\\n---end---"

                        

        expect -re "logout"             {return "$output"; exp_continue}

    注意红色字体的部分,这里是关键代码,用来将所有输出都加入到output变量中去

清空内部缓存

Expect有一个内部缓存用来保存所有字符,一般来说这个缓存就是expect_out(buffer),有的时候我们不希望这个缓存中有内容,因为这些内容可能影响到下一次匹配或者是不需要的内容,此时可以通过下面的小技巧清空缓存中的内容:

    expect “*”

一个简单的 * 号,就可以清空缓存中的内容了。

阻止自动删除空字符

默认情况下,Expect在匹配之前会先将那些没有显示的空字符去掉,这些空字符主要包括某些系统信号,需要注意的是空白字符不是空字符(回车、换行、空格、制表符等都属于空白字符)。

如果想阻止Expect的这个功能,可以使用下面的命令来关闭:

    remove_nulls 0|1        # 0是关闭,1是开启,默认是1

        expect null

    使用null关键字可以匹配这些空字符。

按行匹配

Expect中没有按行匹配的关键字,因此如果希望按行匹配的话,必须要采用另外一种方法,看看下面的示例代码:

spawn ftp 192.168.10.136

expect {

        -timeout 1

        -re "\\[\\r\\n\\]+(Name).+$" {

                puts "\\n=============\\n$expect_out(buffer)\\n============="

                puts "\\n=============\\n$expect_out(0,string)\\n============="

                puts "\\n=============\\n$expect_out(1,string)\\n============="

                close

        }

        default {close}

}

上面的输出如下:

spawn ftp 192.168.10.136

Connected to 192.168.10.136 (192.168.10.136).

220 Serv-U FTP Server v6.4 for WinSock ready...

Name (192.168.10.136:root): 

======================

Connected to 192.168.10.136 (192.168.10.136).

220 Serv-U FTP Server v6.4 for WinSock ready...

Name (192.168.10.136:root): 

======================

======================

Name (192.168.10.136:root): 

======================

======================

Name

======================

上面的表达式中,\\[\\r\\n\\]+是关键,因为这个表达式是使用””来关闭的,所以[]必须用\来关闭本身的含义,而[]之间的\\r\\n则表示匹配回车或者换行符,这是基于兼容性的考虑,后面的那个 + 号则表示必须有一个或多个回车符或换行符,这样不管前面有多少个回车换行都可以匹配了。

从表达式可以看出,前导匹配必须用[\\r\\n]这样的方式,但匹配结尾则简单的多了,一个$字符足矣。

七、个人理解

总体来说,Expect的工作流程就是:spawn启动进程----expect期待关键字----send向进程发送字符。流程虽然是这样,但实际使用时还有一些需要注意的地方:

1.一个expect只能期待一个结果,不能在一个expect中期待多个结果,这一点很容易混淆,最可能的地方就是将单独的expect过程与多个expect过程混淆:

expect –re “login:”

send “username\\r”

expect –re “password”

send “password\\r”

上面的这种方式是发现一个login:字符就发送username字符串,然后开启另外一个匹配过程,匹配password,如果向下面这样写,结果就会不正常了:

expec {

    -re        “login:”        {send “username\\r”}

    -re        “password”    {send “password\\r”}

}

上面的这个过程表示在一次匹配中,如果发现login:字符就发送后面的相应字符串,然后就离开整个过程了,这其中是一个2选1的关系,一定要注意。

2.

文档

Expect 学习笔记

Expect学习笔记————Tim一、Expect介绍Expect是一种TCL扩展性的语言,主要用于完成系统交互方面的功能,比如SSH、FTP等,这些程序都需要手工与它们进行互动,而使用Expect就可以模拟人手工互动的过程,使用一种自动的方式控制。Expect中,有三个重要的主命令,分别是spawn、expect、exp_send,这三个命令几乎存在于所有Expect脚本中,除了这些之外,还有很多Expect所特有的参数、变量,它们也作用与Expect的方方面面。二、Expect命令Expe
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top