expecteof
interact
EOF
....
一、概述
我们通过Shell可以实现简单的控制流功能,如:
循环、判断等。
但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如telnet服务器等进行交互的功能。
而Expect就使用来实现这种功能的工具。
Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。
Expect的作者DonLibes在1990年开始编写Expect时对Expect做有如下定义:
Expect是一个用来实现自动交互功能的软件套件(Expect[isa]softwaresuiteforautomatinginteractivetools)。
使用它系统管理员的可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。
Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。
甚至可以实现实现简单的BBS聊天机器人。
:
)
Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。
Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。
二、Expect工作原理
从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。
Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
Chat脚本由一系列expect-send对组成:
expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。
例如下面的Chat脚本实现等待标准输出出现Login:
字符串,然后发送somebody作为用户名;然后等待Password:
提示符,并发出响应sillyme。
引用:
Login:
somebodyPassword:
sillyme
这个脚本用来实现一个登录过程,并用特定的用户名和密码实现登录。
Expect最简单的脚本操作模式本质上和Chat脚本工作模式是一样的。
例子:
1、实现功能
下面我们分析一个响应chsh命令的脚本。
我们首先回顾一下这个交互命令的格式。
假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:
引用:
#chshchavez
Changingtheloginshellforchavez
Enterthenewvalue,orpressreturnforthedefault
LoginShell[/bin/bash]:
/bin/tcsh
#
可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录shell。
我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。
2、下面是一个能用来实现自动执行该命令的Expect脚本:
#!
/usr/bin/expect
#Changealoginshelltotcsh
setuser[lindex$argv0]
spawnchsh$user
expect"]:
"
send"/bin/tcsh"
expecteof
exit
这个简单的脚本可以解释很多Expect程序的特性。
和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。
程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。
第二个参数使用Expect的spawn命令来启动脚本和命令的会话,这里启动的是chsh命令,实际上命令是以衍生子进程的方式来运行的。
随后的expect和send命令用来实现交互过程。
脚本首先等待输出中出现]:
字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征字符串往往是等待输入的最后的提示符的特征信息)。
对于其他不匹配的信息则会完全忽略。
当脚本得到特征字符串时,expect将发送/bin/tcsh和一个回车符给chsh命令。
最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。
3、决定如何响应
管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。
我们可以通过后面的例子看到expect可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。
下面的例子是一个更复杂的expect-send例子:
expect-re"\[(.*)]:
"
if{$expect_out(1,string)!
="/bin/tcsh"}{
send"/bin/tcsh"}
send""
expecteof
在这个例子中,第一个expect命令现在使用了-re参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。
对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于expect和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。
这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。
当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。
如果不是则发送/bin/tcsh给chsh命令作为输入,如果是则仅仅发送一个回车符。
这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。
在一个正则表达时中,可以在()中包含若干个部分并通过expect_out数组访问它们。
各个部分在表达式中从左到右进行编码,从1开始(0包含有整个匹配输出)。
()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。
4、使用超时
下一个expect例子中将阐述具有超时功能的提示符函数。
这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。
这个脚本接收三个参数:
提示符字串,默认响应和超时时间(秒)。
#!
/usr/bin/expect
#Promptfunctionwithtimeoutanddefault.
setprompt[lindex$argv0]
setdef[lindex$argv1]
setresponse$def
settout[lindex$argv2]
脚本的第一部分首先是得到运行参数并将其保存到内部变量中。
send_tty"$prompt:
"
settimeout$tout
expect""{
setraw$expect_out(buffer)
#removefinalcarriagereturn
setresponse[stringtrimright"$raw"""]
}
if{"$response"=="}{setresponse$def}
send"$response"
#Promptfunctionwithtimeoutanddefault.
setprompt[lindex$argv0]
setdef[lindex$argv1]
setresponse$def
settout[lindex$argv2]
这是脚本其余的内容。
可以看到send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格。
settimeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。
然后expect命令就等待输出中出现回车字符。
如果在超时之前得到回车符,那么set命令就会将用户输入的内容赋值给变脸raw。
随后的命令将用户输入内容最后的回车符号去除以后赋值给变量response。
然后,如果response中内容为空则将response值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。
最后send命令将response变量的值加上回车符发送给标准输出。
一个有趣的事情是该脚本没有使用spawn命令。
该expect脚本会与任何调用该脚本的进程交互。
如果该脚本名为prompt,那么它可以用在任何C风格的shell中。
%seta='prompt"Enterananswer"silence10'
Enterananswer:
test
%echoAnswerwas"$a"
Answerwastest
prompt设定的超时为10秒。
如果超时或者用户仅仅输入了回车符号,echo命令将输出
Answerwas"silence"
5、一个更复杂的例子
下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。
这个例子用来实现发送write命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。
#!
/usr/bin/expect
#Writetomultipleusersfromapreparedfile
#oramessageinputinteractively
if{$argc<2}{
send_user"usage:
$argv0fileuser1user2..."
exit
}
send_user命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。
setnofile0
#getfilenameviatheTcllindexfunction
setfile[lindex$argv0]
if{$file=="i"}{
setnofile1
}else{
#makesuremessagefileexists
if{[fileisfile$file]!
=1}{
send_user"$argv0:
file$filenotfound."
exit}}
这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。
变量file被设置为脚本的第一个参数的值,是通过一个Tcl函数lindex来实现的,该函数从列表/数组得到一个特定的元素。
[]用来实现将函数lindex的返回值作为set命令的参数。
如果脚本的第一个参数是小写的"i",那么变量nofile被设置为1,否则通过调用Tcl的函数isfile来验证参数指定的文件存在,如果不存在就报错退出。
可以看到这里使用了if命令来实现逻辑判断功能。
该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。
if条件为false时则运行else后的程序块。
setprocs{}
#startwriteprocesses
for{seti1}{$i<$argc}
{incri}{
spawn-noechowrite
[lindex$argv$i]
lappendprocs$spawn_id
}
最后一部分使用spawn命令来启动write进程实现向用户发送消息。
这里使用了for命令来实现循环控制功能,循环变量首先设置为1,然后因此递增。
循环体是最后的{}的内容。
这里我们是用脚本的第二个和随后的参数来spawn一个write命令,并将每个参数作为发送消息的用户名。
lappend命令使用保存每个spawn的进程的进程ID号的内部变量$spawn_id在变量procs中构造了一个进程ID号列表。
if{$nofile==0}{
setmesg[open"$file""r"]
}else{
send_user"entermessage,
endingwith^D:
"}
最后脚本根据变量nofile的值实现打开消息文件或者提示用户输入要发送的消息。
settimeout-1
while1{
if{$nofile==0}{
if{[gets$mesgchars]==-1}break
setline"$chars"
}else{
expect_user{
-re""{}
eofbreak}
setline$expect_out(buffer)}
foreachspawn_id$procs{
send$line}
sleep1}
exit
上面这段代码说明了实际的消息文本是如何通过无限循环while被发送的。
while循环中的if判断消息是如何得到的。
在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时while循环也就结束了。
(break命令实现终止循环)。
在交互模式下,expect_user命令从用户接收消息,当用户输入ctrl+D时结束输入,循环同时结束。
两种情况下变量$line都被用来保存下一行消息内容。
当是消息文件时,回车会被附加到消息的尾部。
foreach循环遍历spawn的所有进程,这些进程的ID号都保存在列表变量$procs中,实现分别和各个进程通信。
send命令组成了foreach的循环体,发送一行消息到当前的write进程。
while循环的最后是一个sleep命令,主要是用于处理非交互模式情况下,以确保消息不会太快的发送给各个write进程。
当while循环退出时,expect脚本结束。
三、参考资源
Expect软件版本深带有很多例子脚本,不但可以用于学习和理解expect脚本,而且是非常使用的工具。
一般可以在/usr/doc/packages/expect/example看到它们,在某些linux发布中有些expect脚本保存在/usr/bin目录下。
DonLibes,ExploringExpect,O'Reilly&Associates,1995.
JohnOusterhout,TclandtheTkToolkit,Addison-Wesley,1994.
一些有用的expect脚本
autoexpect:
这个脚本将根据自身在运行时用户的操作而生成一个expect脚本。
它的功能某种程度上类似于在Emacs编辑器的键盘宏工具。
一个自动创建的脚本可能是创建自己定制脚本的好的开始。
kibitz:
这是一个非常有用的工具。
通过它两个或更多的用户可以连接到同一个shell进程。
tkpasswd:
这个脚本提供了修改用户密码的GUI工具,包括可以检查密码是否是基于字典模式。
这个工具同时是一个学习expect和tk的好实例。
另附:
引用:
创建时间:
2001-04-29
文章属性:
转载
文章来源:
中国科大BBS站
文章提交:
quack(quack_at_xfocus.org)
[版权声明]
Copyright(c)1999
本教程由*葫芦娃*翻译,并做了适当的修改,可以自由的用于非商业目的。
但Redistribution时必须拷贝本[版权声明]。
[BUG]
有不少部分,翻译的时候不能作到“信,达”。
当然了,任何时候都没有做到“雅”,希望各位谅解。
[原著]
DonLibes:
NationalInstituteofStandardsandTechnology
libes@cme.nist.gov
[目录]
1.摘要
2.关键字
3.简介
4.Expect综述
5.callback
6.passwd和一致性检查
7.rogue和伪终端
8.ftp
9.fsck
10.多进程控制:
作业控制
11.交互式使用Expect
12.交互式Expect编程
13.非交互式程序的控制
14.Expect的速度
15.安全方面的考虑
16.Expect资源
17.参考书籍
1.[摘要]
现代的Shell对程序提供了最小限度的控制(开始,停止,等等),而把交互的特性留给了用户。
这意味着有些程序,你不能非交互的运行,比如说passwd。
有一些程序可以非交互的运行,但在很大程度上丧失了灵活性,比如说fsck。
这表明Unix的工具构造逻辑开始出现问题。
Expect恰恰填补了其中的一些裂痕,解决了在Unix环境中长期存在着的一些问题。
Expect使用Tcl作为语言核心。
不仅如此,不管程序是交互和还是非交互的,Expect都能运用。
这是一个小语言和Unix的其他工具配合起来产生强大功能的经典例子。
本部分教程并不是有关Expect的实现,而是关于Expect语言本身的使用,这主要也是通过不同的脚本描述例子来体现。
其中的几个例子还例证了Expect的几个新特征。
2.[关键字]
Expect,交互,POSIX,程序化的对话,Shell,Tcl,Unix;
3.[简介]
一个叫做fsck的Unix文件系统检查程序,可以从Shell里面用-y或者-n选项来执行。
在手册[1]里面,-y选项的定义是象这样的。
“对于fsck的所有问题都假定一个“yes”响应;在这样使用的时候,必须特别的小心,因为它实际上允许程序无条件的继续运行,即使是遇到了一些非常严重的错误”
相比之下,-n选项就安全的多,但它实际上几乎一点用都没有。
这种接口非常的糟糕,但是却有许多的程序都是这种风格。
文件传输程序ftp有一个选项可以禁止交互式的提问,以便能从一个脚本里面运行。
但一旦发生了错误,它没有提供的处理措施。
Expect是一个控制交互式程序的工具。
他解决了fsck的问题,用非交互的方式实现了所有交互式的功能。
Expect不是特别为fsck设计的,它也能进行类似ftp的出错处理。
fsck和ftp的问题向我们展示了象sh,csh和别的一些shell提供的用户接口的局限性。
Shell没有提供从一个程序读和象一个程序写的功能。
这意味着shell可以运行fsck但只能以牺牲一部分fsck的灵活性做代价。
有一些程序根本就不能被执行。
比如说,如果没有一个用户接口交互式的提供输入,就没法运行下去。
其他还有象Telnet,crypt,su,rlogin等程序无法在shell脚本里面自动执行。
还有很多其他的应用程序在设计是也是要求用户输入的。
Expect被设计成专门针和交互式程序的交互。
一个Expect程序员可以写一个脚本来描述程序和用户的对话。
接着Expect程序可以非交互的运行“交互式”的程序。
写交互式程序的脚本和写非交互式程序的脚本一样简单。
Expect还可以用于对对话的一部分进行自动化,因为程序的控制可以在键盘和脚本之间进行切换。
bes[2]里面有详细的描述。
简单的说,脚本是用一种解释性语言写的。
(也有C和C++的Expect库可供使用,但这超出了本文的范围).Expect提供了创建交互式进程和读写它们的输入和输出的命令。
Expect是由于它的一个同名的命令而命名的。
Expect语言是基于Tcl的。
Tcl实际上是一个子程序库,这些子程序库可以嵌入到程序里从而提供语言服务。
最终的语言有点象一个典型的Shell语言。
里面有给变量赋值的set命令,控制程序执行的if,for,continue等命令,还能进行普通的数学和字符串操作。
当然了,还可以用exec来调用Unix程序。
所有这些功能,Tcl都有。
Tcl在参考书籍Outerhour[3][4]里有详细的描述。
Expect是在Tcl基础上创建起来的,它还提供了一些Tcl所没有的命令。
spawn命令激活一个Unix程序来进行交互式的运行。
send命令向进程发送字符串。
expect命令等待进程的某些字符串。
expect支持正规表达式并能同时等待多个字符串,并对每一个字符串执行不同的操作。
expect还能理解一些特殊情况,如超时和遇到文件尾。
expect命令和Tcl的case命令的风格很相似。
都是用一个字符串去匹配多个字符串。
(只要有可能,新的命令总是和已有的Tcl命令相似,以使得该语言保持工具族的继承性)。
下面关于expect的定义是从手册[5]上摘录下来的。
expect patlist1action1patlist2action2.....
该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等 到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。
如果最后一个action是空的,就可以省略它。
每一个patlist都由一个模式或者模式的表(lists)组成。
如果有一个模式匹配成功,相应的action就被执行。
执行的结果从expect返回。
被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量expect_match里面。
如果patlist是eof或者timeout,则发生文件结束或者超时时才执行相应的action.一般超时的时值是10秒,但可以用类似"settimeout30"之类的命令把超时时值设定为30秒。
下面的一个程序段是从一个有关登录的脚本里面摘取的。
abort是在脚本的别处定义的过程,而其他的action使用类似与C语言的Tcl原语。
expect"*welcome*" break
"*busy*" {printbusy;continue}
"*failed*" abort
timeout abort
模式是通常的CShell风格的正规表达式。
模式必须匹配当前进程的从上一个expect或者interact开始的所有输出(所以统配符*使用的非常)的普遍。
但是,一旦输出超过2000个字节,前面的字符就会被忘记,这可以通过设定match_max的值来改变。
expect命令确实体现了expect语言的最好和最坏的性质。
特别是,expect命令的灵活性是以经常出现令人迷惑的语法做代价。
除了关键字模式(比如说eof,timeout)那些模式表可以包括多个模式。
这保证提供了一种方法来区分他们。
但是分开这些表需要额外的扫描,如果没有恰当的用["]括起来,这有可能会把和当成空白字符。
由于Tcl提供了两种字符串引