From
漏洞
本来是要把这个程序命名为”sex” (for either "Smart EXec" or "Send-EXpect")。但最后还是以更文雅的方式命名了。在一些系统上,当监视一个Shell时,它会在不能访问tty时仍然运行。这就是说,你的系统有控制tty的机制,但Expect不了解。如果你知道为什么,请告诉我。
Ultrix 4.1(至少是目前最新的版本)会把timeout的值超过1000000时认为是0
Digital UNIX 4.0A(可能是其他版本)如果定义了SIGHLD,会拒绝分配ptys
IRIX 6.0不能正确处理的pty权限,所以如果尝试用前面某个人的pty,它将会失败。请更新到IRIX 6.1
如果没有设置TERM,Telnet会挂掉(只在Sun OS 4.1.2下面证实)。这也是cron,at,和cgi脚本下的问题,因为它们没有设置TERM。因此必须对TERM赋值,赋给它一个不会影响正常运行的类型。下面的设置应该适合大部分情况。
set env(TERM) vt100
如果没有设置SHELL和HOME的话,Tip会挂起(只在BSDI BSD/OS 3.1 i386下证实过)。这也是cron,at,和cgi脚本下的问题,因为它们没有设置这些环境变量。因此必须对这些环境变量赋值,赋给它们一个不会影响正常运行的类型。这些变量是必须要赋值的。下面的语句应该适合大部分情况。
set env(SHELL) /bin/sh
set env(HOME) /usr/local/bin
一些pty的执行被设置成:在进程把文件描述符关闭之后,如果10或是15秒没有读取输出,那么这些输出将被丢弃(具体的时间视执行情况而定)。因此像下面的Expect程序会失败。
spawn date
sleep 20
expect
为了避免这样,可能通过exec执行非交互式程序,而不是使用spawn。这虽然是可信的,但在实践当中,我从来没有遇到在一个交互式程序时,由于上面的BUG而出现丢失字符的现象。另一方面:Cray UNICOS pty会在进程关闭文件描述符的时候立即丢弃所有未读取的输出字符。我已经通知了Cray,它们正在修补这个BUG。有时候,在提示和响应之间需要做一个停顿。例如,当tty正在改变UART设置或是通过检测起始位来匹配波特率的时候。通常,这些都需要停顿1到2秒。一种更智能的技术就是不断尝试,直到硬件准备接收输入为止。下面的代码应用了这两种技术。
send "speed 9600\r";
sleep 1
expect {
timeout {send "\r"; exp_continue}
$prompt
}
trap –code不能正确处理位于Tcl事件环内部的命令,比如说sleep。问题的原因是:在Tcl事件环内部,Tcl会忽略异步事件的返回码。在trap码内部设置一个工作区,里面设置一个标记。然后在命令(也就是sleep)执行之后立即检查标记。
Expect提示
这有一些关于Expect的非直观的东西。这一节通过一些建议讲述这些事情。常见的Expect问题是它怎样标识认出Shell的提示符。因为它们会因用户和Shell的不同而不同。自动登录在不知道提示符的情况下会变得很困难。一个可取的惯例是让用户把一个能够描述这此描述符的常规表达式赋值给一个环境变量EXPECT_PROMPT。代码可以写成如下所示,如果EXPECT_PROMPT不存在,代码也能正常运行。
set prompt "(%|#|\\$) $" ;# default prompt
catch {set prompt $env(EXPECT_PROMPT)}
expect -re $prompt
我建议你把输出字符的结尾包含到你的pattern中,这样可以不用看到整个字符,就可以做出响应。另外,当你可以在不看到整个字符串做出响应的时候,如果你回答的过早,你的答案可能会被显示在问题的中间。换句话说,这样的话,虽然你的结果是对的,但会话记录看起来有点混乱。大多的提示符最后包含一个空格。例如ftp的提示符是“f”,”t”,”p”,”>” 和<blank>。要匹配这个字符串,你要考虑每个字符。丢掉空格是常见的错误,一定要加上空格。
你如果使用”X*”这种模式,那么*会匹配所有从X后开始到最后收到的东西。这听起来很直观,但是“最后收到的东西“让人感觉有点迷惑。这和机器的速度,Kernel和设备驱动处理I/O的方式都有关系。特别的是,人们趋向于认为输出是成块的传输。但实际上大部分程序每次只产生一行输出。如果是这样的话,那么上面的*将只会匹配当前的行尾。虽然可能还会有很多。但在执行匹配的时候,*里面已经是”所有收到的“了。除非模式充分考虑这些情况,要不然Expect不会知道还会有接下来的输出。
把程序设成工作在行缓冲模式下也是不可取的。不仅程序很少把它们的缓冲模式固定,系统处理也可能把输出分隔成多行的。而且分隔点可能在很随机的地方。因此如果在确定模式的时候能够描述出提示符的最近字符,建议你写上它们。
如果你想匹配程序最后输出的字符,但程序却输出了其他的字符,这样你就不可能通过timeout模式来检测到这种情况。原因是Expect不会有超时,相反它会得到一个“遇到文件尾”的信号。使用eof来代替timeout,或是两者都用更好。That way if that line is ever moved around, you won't have to edit the line itself.
终端驱动输出字符的时候一般会把输出中的换行转换为回车,换行的顺序。因此如果你想一个模式能够匹配分别在两行中的字符,例如printf(foo\nbar)的输出,你需要把模式设为"foo\r\nbar"。通过expect_user命令从用户读取信息时也会执行类似的转换。这种情况下,当你按下回车,它将被转换为“换行”,如果这时Expect把它传给一个工作在原始状态的终端上的程序,会产生一个错误,因为程序期望的是一个真正的回车(一些程序设计考虑并解决了这个问题,它会把“换行”转换为“回车”,但大部分不会)。不幸的是,没有办法检测一个程序是否把它的终端设成原始状态。
为了避免手工将“换行”转换为“回车”,解决方法是使用stty raw来阻止这种转换。注意:这样的话,你就不能使用“精加工”模式下的行编辑特性了。
Interact命令隐含的把终端设置成原始模式,这样,上面的问题就不会在它里面出现。
有时在Expect的脚本中存储密码(或其他个人信息)是很有用的。但不建议这样做。因为存在计算机上的东西很可能会受到其他用户的影响。因此,从一个脚本中交互的提示输入密码要比把密码嵌入到脚本内好一些。尽管如此,有时嵌入是唯一可行的办法。
不幸的是,UNIX文件系统不能直接创建可执行而且不可读的脚本文件。支持设置setgid的系统可以间接实现如下:创建Expect脚本文件(包含隐私的数据),设置它的权限位为750(-rwxr-x---),把所有权赋给一个可信任的组。也就是一个可以读取它的组。如果需要的话,可以创建一个这样的组。接下来,以权限2751(rwxr-s--x)建立一个/bin/sh脚本文件,拥有它的所有权限的组和上一个文件一样。结果就是可以被其他人读和执行的脚本。在调用的时候,它执行的是Expect脚本。