天天看点

expect学习笔记-1

        Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。Expect的作者Don Libes在1990年开始编写Expect时对Expect做有如下定义:Expect是一个用来实现自动交互功能的软件套件(Expect is a software suite for automating interactive tools)。使用它系统管理员的可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的BBS聊天机器人。

        在交互式的场景中,当你输入命令后,可能服务器端会返回一些操作提示符,以让你输入命令。Expect提供了这样三个常用的命令,spawn, expect和send,恰好满足这种需要。Spawn命令激活一个Unix程序来进行交互式的运行。send命令向进程发送字符串。expect命令等待进程的某些字符串。expect支持正规表达式并能同时等待多个字符串,并对每一个字符串执行不同的操作。expect还能理解一些特殊情况,如超时和遇到文件结尾。

timeout

       set timeout $tout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-1参数用来关闭任何超时设置)。expect timeout被用于表示所有匹配的失败而和一段特定长度的时间相匹配。默认的timeout时间值为10s。

spawn

        spawn用来启动一个新的进程,比如spawn ssh -D $port [email protected]$host,Expect会执行命令"ssh -D $port [email protected]$host"。从用户的角度来看是像这样的:当一个进程通过spawn命令启动时,变量spawn_id就被设置成某进程的描述符。由spawn_id描述的进程就被认为是当前进程(这个描述符恰恰就是伪终端文件的描述符,虽然用户把它当作一个不透明的物体)。expect和send仅仅和当前进程进行交互。所以,切换一个作业所需要做的仅仅是把该进程的描述符赋给spawn_id。

send/send_user

        用来发送一个字符串,比如 send "hello world"。初始情况下,这个字符串会发送到标准输出。一旦你的程序已经与其他程序进行交互,字符串就会被发送到其他程序那里。send发送的命令一般需要带换行符,在UNIX下可以是"\r"或"\n"。我们使用expect_user和send_user来进行标准I/O,同时不改变spawn_id。

expect/expect_user

       与send相反,expect用来等待你所期望的字符串。expect会一直等待下去,除非收到的字符串与预期的格式匹配,或者到了超时时间。

       expect patlist1 action1 patlist2 action2.....       该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。如果最后一个action是空的,就可以省略它。每一个patlist都由一个模式或者模式的表(lists)组成。如果有一个模式匹配成功,相应的action就被执行,执行的结果从expect返回。  

       缺省情况下,expect "侦听" STDOUT和STDERR,直到找到匹配或timeout期满为止。缺省情况下,调用-glob匹配方式。-glob使用Tcl的字符串匹配方式来匹配模式,它实现文件名替换,支持 "[] [!] ? *" 等通配符,类似于shell模式匹配。-re标志调用regexp匹配,-ex表明必须是精确匹配,不带通配符或变量扩展。expect的其他选项包括-i和-nocase,前者表示要监控产生的进程,后者强迫在匹配之前将进程输出转换为小写。被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量expect_match里面。如果patlist是eof或者timeout,则发生文件结束或者超时时才执行相应的action。缺省情况下超时的时值是10秒,但可以用类似"set timeout 30"之类的命令把超时时值设定为30秒。

        下面的一个程序段是从一个有关登录的脚本里面摘取的。abort是在脚本的别处定义的过程,而其他的action使用类似与C语言的Tcl原语。

expect "*welcome*" break
       "*busy*"    {print busy;continue}
       "*failed*"  abort
       timeout     abort
           

      模式是通常的C Shell风格的正规表达式。模式必须匹配当前进程的从上一个expect或者interact开始的所有输出(所以统配符*使用的非常)的普遍。但是,一旦输出超过2000个字节,前面的字符就会被忘记,这可以通过设定match_max的值来改变。

      expect命令确实体现了expect语言的最好和最坏的性质。特别是,expect命令的灵活性是以经常出现令人迷惑的语法做代价。除了关键字模式(比如说eof,timeout)那些模式表可以包括多个模式。这保证提供了一种方法来区分他们。(在Tcl里面,如果不会出现二意性,没有必要使用引号)。字符可以使用反斜杠来单独的引用,反斜杠也被用于对语句的延续,如果不加反斜杠的话,语句到一行的结尾处就结束了。这和Tcl也是一致的。Tcl在发现有开的单引号或者开的双引号时都会继续扫描。而且,分号可以用于在一行中分割多个语句。这乍听起来有点让人困惑,但是,这是解释性语言的风格,但是,这确实是Tcl的不太漂亮的部分。

      我们使用expect_user和send_user来进行标准I/O,同时不改变spawn_id。

      expect命令使用-re选项,这个选项表示指定的字符串是一个正则表达式,而不是一个普通的字符串。

expect -re "\\\[(.*)]:"
if {$expect_out(1,string)!="/bin/tcsh"} {
 send "/bin/tcsh" 
}
           

      对于上面这个例子里是查找一个左方括号字符(其必须进行多次转义(escape),因为它对于expect和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。在一个正则表达式中,可在()中包含若干个部分并通过expect_out数组访问它们。各个部分在表达式中从左到右进行编码,从1开始(0包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。

interact

       它会把脚本的控制权交给用户,比如你通过脚本自动连接到了某个ftp,并输入了用户名密码,此时需要人工输入一些命令,就可以使用interact命令。控制权交还给用户后,除非子程序运行结束,否则expect程序无法再取得控制权,因此必须在完成所有自动处理的部分后再将控制权交还给用户。如果只是在程序处理过程中要求用户输入信息,可以考虑使用expect_user,以下三种方式都可以获取输入:

expect_user "\n"
expect_user "?*"
expect_user -re ".+"
           

sleep

       sleep n会使程序阻塞n秒。

fork

       fork命令会产生出一个子进程,而且它产生返回值,如果返回的是0,说明这是一个子进程,如果不为0,那么是父进程。disconnect命令将子进程放到后台执行。注意,disconnect命令只能对子进程使用。需要注意的是if和else的写法,if和后面的"{"之间必须要有空格且要放在同一行,else和前面的"}" 后面的"{" 之间也必须要有空格。

 set pid [fork]

 if { $pid==0 } {

  send_user "child\n"

  disconnect

 } else {

         send_user "father\n"

 }

exec

可以用exec来调用Unix程序,并通过[exec cmd]得到命令输出,这就为expect调用shell程序提供了接口。