2008年8月12日星期二

expect

expect 是一种脚本语言,用于编程者与文本界面下交互式程序交互。expect 使用的是 tcl 程序语言,但是 expect 支持一些自己的特殊命令用于实现交互过程。在 debian 的包里,有如下的一些文件:
/usr/bin/expect
/usr/lib/expect5.43/pkgIndex.tcl
/usr/lib/expect5.43/libexpect5.43.a
/usr/lib/libexpect.so.5.43.0
/usr/share/doc/expect/changelog.gz
/usr/share/doc/expect/FAQ.gz
/usr/share/doc/expect/ChangeLog.gz
/usr/share/doc/expect/changelog.Debian.gz
/usr/share/doc/expect/README.gz
/usr/share/doc/expect/copyright
/usr/share/doc/expect/NEWS.gz
/usr/share/man/man1/expect.1.gz
/usr/share/man/man3/libexpect.3.gz
/usr/share/lintian/overrides/expect
/usr/lib/libexpect.so.5
/usr/lib/libexpect.so.5.43
这包括了解释器 expect,也提供了 C/C++ 程序接口库 libexpect.so。

expect 执行的基本方式是,通过自身激活需要交互的程序,并控制该程序的输入输出,这样可以选择通过 expect 脚本与该程序交互(通过分析程序输出内容产生合适的输入),也可以在合适的时候释放对该程序输入输出的控制。因此 expect 本身提供不同于 tcl 的部分主要是在处理这部分功能上。我们会在后面的文章中对 tcl 语言进行简单的介绍,这里仅仅介绍一些 expect 特有的命令:
  • close 用于关闭 expect 打开进程的 stdin,这往往会向该进程发送 EOF,多数程序(如 mail 等)会因此退出,但是不是一定会结束进程(因此可能需要通过 wait 等候进程结束)。-slave 将会把关联到该进程的 pty 也一同关闭。-i 可以指定 spawn id。-onexec 可以为 0 或者非 0,默认为 0 表示该 spawn id 后续进程不受其影响。
  • debug 打开 tcl 调试工具,-now 1 打开调试,-now 0 关闭。
  • disconnect 将一个进程从前台转到后台,这时它的 stdout 等被重定向到 /dev/null。和直接使用 & 不同的地方在于,expect 会存储对应 terminal 信息以便后面将该进程连接到其他的 pty 上。
  • exit 退出该 expect 进程,fork 或者 spawn 的进程根据自己处理 EOF 的方式决定成活。
  • exp_continue 一般在 expect 命令中使用,它可以避免写第二次 expect 命令,即如果匹配上某个模式,然后需要做一些事情(如输入密码什么的),之后需要继续匹配,因此需要第二次 expect 命令。exp_continue 可以重复本次 expect 命令,因此也就不需要再次写一个 expect 命令,而可以将两个写在一起。
  • exp_internal 可以让后续命令的诊断信息发送到 Expect 所控制的 stderr 或者 -f 指定的文件。如果 exp_internal 0 则关闭该功能,非零则打开该功能。
  • exp_open 用来打开(-i)指定的 spawn id,这样打开后对应的 spawn id 不可以再被使用。一种理解是 spawn id 是 expect 层次上交互需要的 id,而通过 exp_open 之后获得的是 tcl 层次上的 id。
  • exp_pid 可以获得(-i)指定的 spawn id 对应的 PID。
  • exp_send 是 send 的别称,send 将指定字符串发送给(-i)对应的进程。-null 发送 ASCII 0 字符,-break 没看懂,-s 慢速发送字符,-h 模仿人打字的速度。
  • exp_send_error 是 send_error 的别称,和前者类似,但是发送到 stderr。
  • exp_send_log 是 send_log 的别称,和前者类似,但是发送到 log file。
  • exp_send_tty 是 send_tty 的别称,和前者类似,但是发送到 /dev/tty。
  • exp_send_user 是 send_user 的别称,和前者类似,但是发送到 stdout。
  • exp_version 可以用来保证源代码解释的 expect 版本,通过 -exit 强制在不合适的情况退出。
  • expect 是最重要的命令,它通过匹配某个 spawn 程序的输出执行某个命令,如输出某些与该程序交互或者转移对程序的控制等等。基本的用法是 -opt pat command,即通过 -opt 指定特定的属性(如 spawn id 等),然后匹配 pat,执行 command(多个 command 可以用 {} 括起来,同行命令用 ; 分隔),一个 expect 可以带有多组这种结构应对不同的情况。常用的 opt 有 -i 指定 spawn id,-re 使用 tcl 的正则表达式,-ex 进行精确匹配(如忽视 ^ 和 $ 表达字符串首尾的意思,和一般 RE 里面使用行首行尾是不同的),-nocase 不区分大小写。pat 可以用关键字如 eof、timeout(超时用 set timeout 设置,默认是 10s)、default(eof 或者超时)。或者用 glob 方式匹配(有空格时应该用引号引起来)。匹配时 expect 维护一个 buffer,默认容纳 2000 字节(可用 match_max 函数设定),因此超过该长度的输出会被截断。匹配时,会设置一些相关的变量,如 expect_out(buffer) 是匹配上的内容及其之前没匹配上的内容,剩下的还在 buffer 里可供下次继续匹配。另外 expect_out(X, string/start/end) 在 X=0 时表示匹配上的整个字符串及其始末位置,而 X = 1..9 时表示每一个匹配部分的类似信息(需要 -indeices)。每个 spawn id 对应的 buffer 在 expect_out(spawn id)。不使用 -i 指定 spawn id 时,使用的是 any_spawn_id 这个变量。
  • expect_before 和 expect_after,这两个作为 expect 的补充,一个有限比 expect 高,一个低。
  • expect_background 在后台匹配,因此不存在 timeout,同时会立即返回。在执行匹配好后的命令时会 block 后台处理。
  • expect_tty 和 expect_user 与 expect 类似,但是输入来源分别是 /dev/tty 和 stdin,一般需要用户回车后才能匹配,可以通过 stty 改变 tty 的模式不需要回车也能匹配。
  • fork 产生一个新的 expect 进程,子进程返回 0,一般在 spawn 前 fork 比较安全。
  • interact 是一个非常重要的命令,它允许用户参与到交互过程中,它捕获用户的输入进行匹配(用户自定义的字符串),如果匹配成功(和 expect 不同之处是这里都是精确匹配)则执行用户自定义的命令。格式为 -opt string command。其中 opt 可以是 reset 表示将 terminal 设置成为 interact 命令前的状态,re 打开正则表达式匹配,iwrite 将 interact(spawn_id) 设置为该 spawn id,echo 将用户输入直接回显(如果没匹配上就会显示两遍),nobuffer 将直接把输入字符输出到进程,u spawn_id 可以让用户与指定 spawn_id 交互,input/output 用于控制指定 spawn id 的输入输出(如果没有 output 则丢弃)。string 可以是 eof、timeout N(表示等待的时间)、null(接受到 ASCII 码 0)。一种用法就是利用 expect 提供更简单的命令名。
  • interpreter 打开 expect/tcl 的解释器,用于交互执行 expect 命令。
  • log_file 记录会话过程,可以通过 -a 指定文件,可以提供 tcl 文件 identifier(-open、-leaveopen)。
  • log_user 记录 expect 和 send 会话,log_user 0 关闭,log_user 1 打开。
  • match_max 设定 expect 匹配的 buffer 大小。
  • overlay 将该 expect 进程用指定程序代替,可以通过 -n 指定 stdin/out/err 对应绑定的进程(类似于管道)。
  • parity 没看懂...
  • remove_nulls 决定 spawn 进程的 null 是不是被去掉。
  • sleep 休息若干秒。
  • spawn 启动一个进程,并设定几个相关的变量,如 spawn_id 包含该进程的信息(expect 根据这个判断输出),user_spawn_id 含有的是和用户相关的信息(因此将 spawn_id 设为此值就成为了 expect_user),类似还有 error_spawn_id 和 tty_spawn_id。spawn_out(slave,name) 是该进程连接到的 pty。-ignore 将使得进程忽略掉某些信号。
  • strace 提供调试程序的功能。
  • stty 和外部命令 stty 类似,设定 terminal 的模式,-raw 还是 -cooked。开关回显 echo 和 -echo。
  • system 执行某个命令,和 exec 不同之处在于并不将其输入输出定向到 /dev/tty。
  • timestamp 返回时间戳。
  • trap 将对应的 signal 绑定到某个 command,即设置 signal handler。
  • wait 等候 spawn 进程结束。
需要使用 C 程序接口可以用 libexpect。

下面是一个建立 ssh tunnel 的简单例子:
#!/usr/bin/expect -f

while {1} {
spawn ssh host -R 52028:127.0.0.1:22 -N
wait
sleep 60
}