expect说明

原文:http://hi.baidu.com/liuhangbin/item/a9c3c3d882ca5e3c48e1dd10

样式匹配

*

expect "hi*"
send "$expect_out(0,string) $expect_out(buffer)"

输入philosophic,输出为hilosophic philosophic,hi*匹配的是hilosophic

如果是hi*hi,则匹配的是hilosophi

如果是*hi*,则匹配的是philosop hi c\n ,而不是p hi losophic\n,因为匹配是从左到右进行,且*尽可能匹配更多的字符,但是要符合样式,所以第一个*匹配的字符多一些

*开头的样式并不经常用到,像*hi*,它能把前面不匹配的数据保存在expect_out(0,string),但是expect_out(buffer)中也会保存,所以这点也没什么意义

*结尾的样式经常需要认真考虑,因为程序的输出不像人的标准输入一样是一行一行而是一堆一堆进行,这样可能匹配的最多数据还没发送完,程序已经返回了。因为*可以匹配任何东西,包括空字符串,可以用它做结尾匹配人的一些输入,并清理输入缓存。

更多Glob样式

处理超时

expect "hi"
expect "hi" {}
expect {
       "hi"     {send "You said hi\n"}
       "hello"  {send "Hello yourself\n"}
       "bye"
}

只有expect命令中最后一个动作可以省略

事实上,用来超时代替一切,会增加程序的反应时间,但要处理每种错误,就要在脚本上花费很多时间。通常用超时处理一些模糊的条件,而用一个样式处理大多数的错误,例如ftp的错误都是以4或者5开头,例子:

expect {
       timeout {unexpected...}
       "^\[45]" {error...}
       "ftp>"
}

可以用echo $?察看脚本的返回值,可以为timeout增加一些内容输出,因为ping有返回的话自会打印结果,如果超时,脚本会在ping返回结果以前将其终止并结束,所以脚本要增加输出内容:

spawn ping $host
set timeout 2
expect "alive" {exit 0} timeout{
       puts "no answer from $host"
       exit 1
}

文件结尾处理

在以上maxtime脚本中,如果程序先结束,则脚本随之结束,因为没有要匹配的样式,所以程序如果在超时以前没有结束,脚本也会返回。

#!/usr/bin/expect -
set timeout [lindex $argv 0]
eval spawn [lindex $argv 1 end]
expect {
       timeout {puts "took toomuch time"}
       eof {puts "finished in time"}
}

maxtime 2 sleep 5%maxtime 5 sleep 2返回的结果是不一样的,eof和timeout一样,也是一个特殊的匹配样式,表示程序结束。

eval spawn [lrange $argv 1 end]来执行spawn命令,因为spawn会将它后面lrange取得的所有参数当作一个命令。

如果ping发现主机不存在,返回eof,则expect正常结束,它返回0,正常情况下脚本执行成功才返回0,但是这种情况下主机不存在也会返回0,所以要对eof处理,修订脚本如下:

spawn ping $host
set timeout 2
expect "alive" {exit 0} timeout {exit 1} eof {exit 1}

如果对超时和异常结束作相同的处理,可以default样式,它可以匹配所有可能的情况: expect "alive" {exit 0} default {exit 1}

close命令

eof不仅可以从process送往expect,也可以从expect送往process,使process退出,在expect脚本中用close显式结束一个进程,通常情况下,process和expect一个结束了,另一个也会随之结束。

忽略eof的程序

一类是运行在Raw模式下的程序,像telnet,这种模式下,不对输入的字符作特殊的翻译,^c,^d都不起特殊的作用,只是普通字符,eof也是,所以没有正常退出的话要手动杀死进程,用kill命令。

第二类是看到eof就忽略已经接收到的字符马上退出的进程,像ftp,vi,下面的脚本:

spawn ftp...
#assume username and password are accepted here
expect "ftp>" {send "get file1\r"}
expect "ftp>" {send "get file2\r"}
expect "ftp>" {send "get file3\r"}

上面的脚本只能接收到两个文件,因为在收到第三个文件的请求时也收到eof,因为脚本执行完毕,0所以退出,解决办法是在行末加一句epect命令,使脚本不能提前结束。

spawn vi file
send "ifoo\033:wq\r"

错误,文件没有内容,正确的如下:

spawn vi file
send "ifoo\033:wq\r"
expect

wait命令

wait 等待一个进程死亡,-noflag用来避免时延。

expect重要的命令

send命令

send "hello world"
send "hello world\n"
expect speak        #speak为一脚本

expect命令

expect "hi\n"
send "hello there!\n"
expect_out(0,string)  # 用来保存匹配的字符,匹配字符和它以前的字符保存在expect_out(buffer)
expect "hi\n"
send "you typed <$expect_out(buffer)>"
send "but I only expected <$expect_out(0,string)>"

定位

此时输入philosophic,输出为hi phi

如果再次执行这两句命令,输出为hi losophi

input buffer用来保存expect取得的输入字符串,第一匹配结束后,expect_out(0,string)hi,expect_out(buffer)phi,下一次的匹配从losophi开始,所以上面的两个值分别为hi,losophi,此时input buffer里为c\n,因为不能够匹配hi,所以如果进行第三次匹配,会超时,而上面的expect_out(0,string) expect_out(buffer)与第二次相同,所以第三次超时后会输出hi losophi

INTERGER为一整数值,如果为-1表示永久等待,如果为0表示立即返回

命令会对hi,hello,bye同时进行匹配 注意书写格式,expect "hi" send "You said hi\n"#错误,You said hi会被解释成一个命令

expect "exit" {exit 1} "quit" abort为写在一行的格式,匹配命令带参数的话一定要用大括号括起来。

timed-read 60作用是等待60,接收此段时间内用户的输入。

expect {
#!/usr/bin/expect -
set timeout $argv
expect "\n" {
     send [string trimright "$expect_out(buffer)" "\n"]
}

spawn命令

spawn命令用于启动一个进程

例子ftp-rfc

#!/usr/local/bin/expect -
#retrieve an RFC (or the index) from uunet via anon ftp
if { [llength $argv]== 0} {
       puts "usage: ftp-rfc {-index|#}"
       exit 1
}
set timeout -1
spawn ftp ftp.uu.net
expect "Name"
send "anonymous\r"
expect "Password:"
send don@libes.com\r
expect "ftp> "
send "cd inet/rfc\r"
expect "ftp> "
send "binary\r"
expect "ftp> "
send "get rfc$argv.z\r"
expect "ftp> "
exec uncompress rfc$argv.z

例子中binary的命令在于让ftp传文件时不要作任何转换,注意结尾是\r,不是\n,视为手动输入和机器输入的差别?? 最后一句的作用是解压缩。

interact命令

interact命令用于脚本执行完简单的命令后人手动介入,只在所属的spawn进程空间有效

例子aftp

#!/usr/bin/expect -
spawn ftp $argv
expect "Name"
send "anonymous\r"
expect "Password:"
send don@libes.com\r
interact
可以用send "$env(USER)@libes.com\r"
proc domainname {} {
       set file [open /etc/resolv.conf r]
       while { [gets $file buf] !=-1} {
              if {[scan $buf "domain %s" name]==1} {
                     close $file
                     return $name
              }
       }
       close $file
       error "no domain declaration in /etc/resolv.conf"
}
send "$env(USER)@[domainname]\r"

其它

expect的几种格式

其中的第一种情况和情况二是两种完全不同的语义。

对于第一种情况来说,它是一个多选一的过程,对于特定任务的输出,只要其中的任何一个pattern能够匹配,那么这个expect就算是完成了一次匹配。

第二种情况则表示说进行若干次各自的匹配,所有的匹配被依次满足了之后才算整个语句完成。这一点就和前面说的tcl的命令解析方式有关了,因为第一种格式虽然占用的行数多,但是它是一个单独的expect命令,所以expect可以一次性将所有情况解析出来,然后组成一个大的并行分支;对于第二种,expect看到的只是多条单独的expect匹配,根据每个expect必须又一次匹配的原则,需要有多次匹配。总之简单的说,一个是并行关系,一个串行关系。

第三种是expect的一种特殊用法,虽然很少有人这么做,但是如果真的有人这么你也不能说错。它的意义同样是完成一次匹配,并且匹配之后不执行任何动作,同样是只要不匹配就继续执行。