Expect模块可以用来向需要交互执行的程序输入必要的信息。以脚本形式实现交互式应用程序的语言。
代码:
$exp = new Expect;
#new一个Expect.
$exp = Expect->spawn("ssh $host");
$exp->spawn($command,@params);
#fork和exec一个新进程用来执行$command命令.
$exp->expect($timeout,$content);
#'-re'.在$timeout内,期待出现包含$content内容的正则表达式,如果不加re默认是精确匹配,也可以使用正则表达式,expect ($timeout,re=>'').
$exp->send($string);
#向终端发送字符
$exp->debug(0|1|2);
#打印debug 信息,不同的数字表示不同的bug级别
$exp->interact();
#和用户进行交互,把控制权转交给用户
$exp->soft_close();
#软关闭,直到$timeout时间到达,才关掉该进程
$exp->hard_close();
#硬关闭,立刻关闭该进程
#my $read = $exp->before();
#匹配字段之前的,相当于$`
#my $read = $exp->after();
#匹配字段之后的,相当于$'
$exp->match();
#返回匹配的结果,相当于$&
$exp->match_number();
#返回匹配的个数
$exp->send_slow();
# 就是让command慢慢敲进去..
$exp->stty();
#它和外部命令 stty 类似,设定 terminal 的模式,-raw 还是 -cooked。开关回显 echo 和 -echo。
我们想让程序只在后台跑就行了,不会跑到前面来影响我们.所以我们需要expect的关掉终端显示的功能.
代码:
$Expect::Log_Stdout = 0;
$exp->log_stdout(0);
注:0为关掉显示,1为打开
希望expect一直等到某个字符出现需要怎么样做
代码:
$exp->expect(undef,sub {});
如果不希望expect等,就使用$exp->expect(0),这样当前如果有这个字符就成功,没有就跳过.
在使用expect时,日志中有很多特别的符号,使用下面这个terminal type的设定,就会好了. 不知为什么,日志文件中会有很多^[ ,^G之类的字符,如果用了下面的参数就行了,只有一个^M,这个用dos2unix就行了
代码:
$ENV{TERM} = "xterm";
打开内部调试,这样可以让后续命令的诊断信息发送到 Expect 所控制的 stderr,这样可以显示出比如$exp->expect()中的内容是否匹配.这样可以显示看到显示的字符,方便调试匹配规则,
代码:
$exp->exp_internal(1);
注意:internal这个必须写到spawn后面 打开debug模块,可以显示连接的过程,进程的退出之类的状态
代码:
$exp->debug(3);
记录expect的日志到一个文件
代码:
$exp->log_stdout(1);
$exp->log_file("output.log");
#注意,这句一定要写在Expect->spawn之后,不然expect会没有日志输出
中间发送和匹配之类的过程.......
代码:
$exp->log_file(undef);
在expect时,追加日志到一个文件
代码:
$exp->log_file("filename");
默认是追加,如果要修改就用
代码:
$exp->log_file("filename", "w");
在perl的expect中怎么发送控制字符,
比如
代码:
ctrl+c control-G $exp->send(”\cG”);
control-c $exp->send(”\cC”;);
1 一个简单的Perl使用Expect自动登陆服务器的实例
2
3 代码:
4 #!/usr/bin/perl
5 use Expect;
6 $Expect::Log_Stdout = 1;
7
8 $ENV{TERM} = "vt100";
9
10 my ($host,$pass) = ("host","passwd");
11 my $exp = Expect->new;
12 $exp = Expect->spawn("ssh -l root $host");
13 $exp->log_file("output.log", "w");
14 $exp->expect(2,[
15 qr/password:/i,
16 sub {
17 my $self = shift ;
18 $self->send("$pass\n");
19 #exp_continue;
20 #网上一堆二逼就直接抄人家的,看也不看。他妈的明明是先输了yes后才输密码的,这里continue你妹啊!
21 }
22 ],
23 [
24 'connecting (yes/no)',
25 sub {
26 my $self = shift ;
27 $self->send("yes\n");
28 exp_continue;
29 }
30 ]
31 );
32 #$exp->interact() if ($exp->expect(undef,'#'));
33 $exp->send("uptime\n") if ($exp->expect(undef,'#'));
34 $exp->send("exit\n") if ($exp->expect(undef,'#'));
35
36 $exp->log_file(undef);
这个是我自己的例子:
1 #!/usr/bin/perl
2
3 use Expect;
4
5 $Expect::Log_Stdout = 1;
6
7 my $host = $ARGV[0];
8
9
10 my $exp = Expect->new;
11
12 $exp = Expect->spawn("ssh $host");
13
14 $exp->log_stdout(1);
15 #$exp->exp_internal(1);
16 #$exp->log_file("output.log");
17
18 #$exp->notransfer(0);
19
20 $exp->expect(2,[
21 qr/password:/i,
22 sub {
23 my $self = shift ;
24 $self->send("pdg.net\n");
25
26 }
27 ],
28 [
29 'connecting (yes/no)?',
30 sub {
31 my $self = shift ;
32 $self->send("yes\n");
33 exp_continue;
34 }
35 ]
36 );
37
38 #$exp->interact() if ($exp->expect(undef,'#'));
39
40 #$exp->expect(undef,'#');
41
42
43
44
45
46 $exp->send("ip a s | grep 180\n") if ($exp->expect(undef,'kobe:~#'));
47
48 #my $read = $exp->before();
49 #print "=== $read ===";
50
51 #my $read = $exp->after();
52 #print "=== $read ===";
53
54
55
56
57 $exp->send("exit\n") if ($exp->expect(undef,'#'));
58 #$exp->log_file(undef);
下面的太多了,有空再看!
下载和安装
Expect 和 Expect::Simple 都可以从 CPAN 网站上直接下载,最新版本为 Expect-1.21 和 Expect::Simple-0.04。比较方便的安装方法是使用 Perl 自带的包管理工具 cpan:
清单 1. 模块安装
perl -MCPAN -e 'install Expect::Simple'
|
请确保有足够的执行权限,cpan 将自动解决模块间依赖关系。由于 Expect::Simple 依赖于 Expect,安装 Expect::Simple 的过程中,Expect 模块以及另外的依赖模块都会自动安装完毕。
Expect 模块详解
与最初的 Expect 语言类似,Expect 模块的主要功能也通过 spawn,expect,send 三个方法实现。
spawn:启动目标程序
清单 2. spawn 方法原型
$obj = Expect->spawn( $command, @parameters );
|
spawn 是 Expect 类的主要方法之一,通过 fork 和 exec 启动目标程序,若成功则返回 Expect 对象,否则返回 undef。参数 $command 指定目标程序,@parameters 为可选参数。下面是一个简单的例子:
清单 3. spawn 用法示例
$obj1 = Expect->spawn( "ftp 9.9.9.9" ); # 启动 ftp 进程
$obj2 = Expect->spawn( "ftp", "9.9.9.9" ); # 与上一行等效
|
上述两行执行结果相同,但其实际处理过程存在细微差别。一般情况下我们可以把完整的命令行 ( 甚至可以是复合命令,包括命令、参数、管道符、重定向符等 ) 都写入 $command 而不指定 @parameters。
注:在 spawn 的实现中,$command 和 @parameters 都原封不动地传递给了 Perl 的 exec 函数。根据 exec 函数的说明文档,如果传递进来的是多元列表参数,exec 直接将其传递给 execvp 系统调用;如果传递进来的是标量参数或者单元列表参数,exec 函数将检查是否存在 shell 元字符 ( 如 | & ; ( ) < > 等 ),若存在,则将此参数交给系统 shell 进行解析,否则将其分词后传递给 execvp 系统调用。因此如果 spawn 的是一个含有 shell 元字符的复合命令,我们一般只能将其完整写入 $command。
expect:等待特定输出
清单 4. expect 方法原型
$obj->expect( $timeout, @match_patterns );
|
使用 Expect 对象的 expect 方法等待目标程序的特定输出。参数列表中 $timeout 设定超时 ( 以秒为单位 ),@match_patterns 提供一个或多个匹配模式,如果在设定时间内目标程序输出结果和 @match_patterns 中某元素匹配则成功返回。缺省情况下 expect 使用精确匹配,若想使用正则表达式,可以在该模式元素前加 '-re' 前缀 :
清单 5. 启用正则表达式匹配
$obj->expect( 10, 'match me exactly', '-re'=>'match\s+me\s+exactly' );
|
标量上下文中 expect 返回匹配模式在 @match_patterns 中的位置 ( 注意下标从 1 开始 ),若不成功则返回 undef。而列表上下文中 expect 返回一个包含详细匹配信息的列表:
清单 6. expect 方法的列表返回值
( $pos, $err, $match, $before, $after ) = $obj->expect( $timeout, @patterns );
|
其中 $pos 就是在标量环境中的返回值,$err 是出错信息,$match 为成功匹配的字串,$before 为匹配字串之前的输出部分,$after 为匹配字串之后的输出部分。下面这个例子 (matchinfo.pl 及其输出结果 ) 具体说明了这些值的含义:
清单 7.matchinfo.pl
#! /usr/bin/perl
# 9.125.13.44 是一台 ftp 服务器,使用 ftp 命令登录时的输出为:
# Connected to 9.125.13.44.
# 220 AIX6144 FTP server (Version 4.2 Tue Dec 22 14:13:26 CST 2009) ready.
# 502 authentication type cannot be set to GSSAPI
# 502 authentication type cannot be set to KERBEROS_V4
# KERBEROS_V4 rejected as an authentication type
# Name (9.125.13.44:root):
use Expect;
$obj = Expect->spawn( "ftp 9.125.13.44" ) or die "Couldn't spawn ftp, $!";
# 关闭目标程序的输出显示
$obj->log_stdout( 0 );
# 使用 slice 获取列表环境下的返回值
@result{ "position", "error", "match", "before", "after" } = $obj->expect( 10, 'Name' );
# 查看匹配结果
print "$_ = $result{$_}\n" foreach ( keys %result );
# 关闭目标程序
$obj->soft_close( );
|
为了使运行结果更加清楚,示例中使用$obj->log_stdout(0)关闭目标程序的输出显示。该程序的运行结果为:
清单 8.matchinfo.pl 输出结果
after = (9.125.13.44:root):
match = Name
error =
position = 1
before = Connected to 9.125.13.44.
220 AIX6144 FTP server (Version 4.2 Tue Dec 22 14:13:26 CST 2009) ready.
502 authentication type cannot be set to GSSAPI
502 authentication type cannot be set to KERBEROS_V4
KERBEROS_V4 rejected as an authentication type
|
Expect 对象也提供了单独的方法来获得这些匹配信息,如$obj->before( ),$obj->after( ),$obj->match( ),$obj->error( ) 等。
使用 -i 选项可以对多个或多组 Expect 对象同时执行 expect 操作:
清单 9. -i 选项
$obj1 = Expect->spawn( "ftp 9.125.13.44" ) or die $!;
$obj2 = Expect->spawn( "telnet 9.9.9.9" ) or die $!;
$obj3 = Expect->spawn( "ssh 9.181.59.64" ) or die $!;
expect ( $timeout,
'-i', $obj1, '-re', qr/name/i,
'-i', [$obj2, $obj3], '-re', qr/login/i, '-re', qr/password/i,
)
|
本例使用函数风格的 expect 方法 ( 区别于通过对象调用 ),构造匿名数组的引用 [...] 来传递多个 Expect 对象。
此外在 $obj->expect($timeout, @match_patterns)中,@match_patterns 还可采用 [ $regexp, sub{},@opt_params ]的形式,根据不同模式来执行不同后续操作。逻辑流程较为简单时,利用此特点可以使代码组织更加紧凑。下面的 telnet 登录脚本 (exptelnet.pl) 采用了这种形式:
清单 10.exptelnet.pl
#! /usr/bin/perl
use Expect;
my $PROMPT = '[\]\$\>\#]\s*$'; # 远程系统的命令提示符模式
$obj = Expect->spawn( "telnet 9.125.13.44" ) or die "Couldn't spawn telnet, $!";
$obj->expect( 10,
[ qr/login:\s*$/i,
sub{ my $self = shift; $self->send( "root\r" ); exp_continue;}
],
[ qr/password:\s*$/i,
sub{ my $self = shift; $self->send( "zu88jie\r" ); exp_continue;}
],
[ qr/$PROMPT/,
sub{my $self=shift; $self->send( "logout\r" ); exp_continue_timeout;}
],
);
|
示例中匿名函数返回 exp_continue 符号将重置等待时间并继续执行 expect,使得一次 expect 调用可以完成多次匹配动作。与之相对的是返回exp_continue_timeout 符号,在继续执行 expect 时不重置等待时间。
send:发送数据
清单 11. send 方法原型
当交互式程序等待用户输入时,可以使用 send 方法向其提供输入数据。需要注意,send 送出的数据可能会回显在终端上 ( 具体与终端设置有关 ),此数据会进入 Expect 对象的匹配缓冲区,被下一个 expect 动作接收。为了避免 send 数据对 expect 匹配造成混乱,一般可以使用 $obj->stty("-echo" )方法关闭终端回显,或者在 spawn 前使用 $obj->raw_pty(1)将终端设定成 raw 模式 (raw 模式将关闭回显,禁止回车 - 换行符翻译 ),或者为expect 提供更加精确的匹配模式。
log_file:设置日志记录
清单 12. log_file 方法原型
$obj->log_file( $filename | $filehandle | undef );
|
将交互过程的内容输出到日志文件能便于自动化脚本的追踪和调试。通常使用文件名或者文件句柄作为参数来指定具体日志文件,例如:
清单 13. 设置日志记录
$obj = Expect->spawn( "ftp 9.9.9.9" );
# 使用"w"选项截断旧日志
$obj->log_file( "./out.log", "w" );
|
debug:设置调试信息
清单 14. debug 方法原型
$obj->debug( 0 | 1 | 2 | 3 );
|
参数 0 为禁止调试信息 ( 缺省值 ),1 ~ 3 级详细程度递增。另一个相关的方法是 $object->exp_internal( 1 | 0 ),用以打开或关闭expect 对象的内部调试信息。将此标志设置为 1 可以看到expect 对象对输出内容尝试匹配的完整过程 (expdebug.pl):
清单 15.expdebug.pl
#! /usr/bin/Perl
use Expect;
$obj = Expect->spawn( "ftp 9.125.13.44" ) or die $!;
$obj->exp_internal( 1 ); # 打开对象内部调试信息
$obj->expect( 10, "Name" );
$obj->soft_close( );
|
运行结果为:
清单 16.expdebug.pl 输出详细匹配过程
Starting EXPECT pattern matching...
... 省略中间输出 ...
spawn id(3): Does `'
match:
pattern #1: -ex `Name'? No.
... 省略中间输出 ...
match:
pattern #1: -ex `Name'? YES!!
Before match string: `Connected to 9.125.13.44.\r\n220 AIX6144 FTP server ...'
Match string: `Name'
After match string: ` (9.125.13.44:root): '
Matchlist: ()
|
从结果中可以监视目标程序每一条输出与 expect 模式的匹配情况,若匹配成功还能查看 before\match\after字串,这对于调试程序大有帮助。
interact:返回交互模式
清单 17. interact 方法原型
为了适应某些特殊场合,我们可能需要将控制权交还给目标程序,此时只需使用 $obj->interact( )方法。
clear_accum 与 set_accum:操纵匹配缓冲区
清单 18.clear_accum 与 set_accum 方法原型
$obj->clear_accum( );
$obj->set_accum( $value );
|
expect 方法针对 Expect 对象的匹配缓冲区 (accumulator) 进行匹配尝试,默认情况下每次匹配成功后,accumulator中 before 和 match 部分将被清除,下次匹配从 after 开始。但是 Expect 对象提供了 clear_accum 与 set_accum 方法改变这种行为:使用 $obj>set_accum( $value )将缓冲区内容设置成 $value,使用 $obj>clear_accum( )清空缓冲区内容。具体用法参见如下代码片段:
清单 19. 操纵匹配缓冲区内容
$obj->notransfer( 1 );
$obj->expect( $timeout,
# 1 保留 accumulator 内容 , pattern1 将被再次匹配
[ "pattern1",
sub { my $self = shift; ... }
],
# 2 将 accumulator 内容设置为 after string,即截断 before 和 match string
[ "pattern2",
sub { my $self = shift; ...; $self->set_accum( $self->after( ) );}
],
# 3 将 accumulator 内容清空
[ "pattern3",
sub { my $self = shift; ...; $self->clear_accum( );}
],
);
|
示例中 $obj->notransfer( 0 | 1 )方法用于设置是否保留匹配缓冲区内容。0 是默认行为 ( 清除 before 和 match);1 为保留所有内容。保留缓冲区内所有内容会导致原先匹配过的模式被再次匹配 (#1)。我们也可手动设置 accumulator 内容来影响下一次匹配 (#2 #3)。
Expect::Simple 模块详解
Expect::Simple 对 Expect 模块进行了封装,隐藏其内部复杂机制。此模块处理一些简单的应用已经足够。
new;构造方法
清单 20. new 方法原型
$obj = Expect::Simple->new( \%attr );
|
创建 Expect::Simple 对象的同时启动目标程序,需传递关联数组 %attr 的引用作为参数。通过该关联数组设置目标程序的相关信息 ( 如命令、超时、提示符等 ),因此该数组必须包含 Prompt,DisconnectCmd,Cmd 等键值。
Cmd 键指定目标程序及其参数。它的值可以是标量或者是数组引用:Cmd =>$command或 Cmd => [ $command, $arg1, $arg2, ...]。在 Expect::Simple 的实现中 $command 或 $command, $arg1, $arg2, ...都被直接传给 Expect 的 spawn 方法,因此前面对 spawn 方法的分析在这里同样适用:可以把完整的命令行写入 $command;对不含 shell 元字符的简单命令,也可以使用分拆形式,传递数组引用。
Prompt 键指定一个或一组预期的输入提示 ( 支持正则表达式 ):Prompt => ['ftp>', 'telnet>',-re => 'prompt\d+>\s+']。Prompt 键值相当于 Expect 模块中 expect 方法的匹配模式参数。
DisconnectCmd 键指定退出目标程序所用的指令:DisconnectCmd=> 'exit'。
Timeout 键设定超时 ( 缺省值为1000 秒 ),如果目标程序在设定时间内未响应则返回。Expect::Simple在目标程序启动之前设置超时且使用全局设定,因此无法区分处理目标程序执行过程中立即响应部分和有明显延时的部分。在设定超时值时需要考虑响应最慢的阶 段。
RawPty 键用于设置终端,对应于 Expect 模块的 raw_pty 方法,默认值为 0。
Verbose 键用于设定输出内容的详细程度:Verbose => 3。
send:顺序发送数据
使用 $obj->send( $cmd | @cmds)向目标程序发送一条或依次发送多条数据。每条数据送达后等待下一个输入提示以发送下一条数据。以 ssh 自动登录为例,serialsend.pl 演示如何顺序发送多条数据:
清单 21.serialsend.pl
#! /usr/bin/perl
use Expect::Simple;
my %attr = (
Prompt => [ -re => qr/password:\s*$/i,
-re => qr/[\]\$\>\#]\s*$/ ],
Cmd => 'ssh root@9.125.13.44',
DisconnectCmd => 'exit',
Verbose => 3,
Timeout => 10,
);
my $obj = Expect::Simple->new( \%attr );
# 自动输入密码,执行 ls 命令,退出。由于已设定退出命令,此处不必再发送 exit
$obj->send( "zhu88jie", "ls" );
|
输出结果:
清单 21.serialsend.pl 执行结果
Running command...done.
Sending `zhu88jie'
Sending `ls'
Disconnecting.
|
若想看到 ls 命令的执行输出,只需将 Verbose 值设为 4。
before,after,match_str,error:查看匹配信息
这些方法和 Expect 模块中的 before,after,match,error 一一对应,不再详细阐述。需要注意由于 Expect 对象的缓冲区在匹配过程中会不断更新,在依次发送多条数据的情况下,使用这些方法只能查看最近一次匹配结果。
expect_handle:使用内部对象
Expect::Simple 封装了 Expect 模块的细节,提供简单易用的接口,但是当 Expect::Simple 提供的功能无法以满足需求时,还可以使用这个方法直接操作内部 Expect 对象。
应用示例
Expect 模块使用示例
本例 (ftpdemo.pl) 演示如何使用 Expect 模块从 ftp 上自动下载文件。
清单 22.ftpdemo.pl
#!/usr/bin/perl
# Usage: ftpdemo.pl [-u username] [-p password] host file1 [file2 file3 ...]
use Expect;
use Getopt::Std;
# 设置缺省用户名和密码
my %opts = ( u=>'anonymous', p=>'anonymous@mycompany.com' );
# 解析 -u 和 -p 选项
getopt( 'up', \%opts );
$host = shift @ARGV; # 下一个参数是 ftp 服务器地址
@files = @ARGV; # 余下的参数为需要下载的文件
# 启动 ftp 进程
print "Starting ftp session with server $host ...\n";
$ftp = Expect->spawn( "ftp $host" ) or die "Couldn't spawn ftp, $!";
# 屏蔽多余输出
$ftp->log_stdout( 0 );
# 等待用户名输入提示
unless ( $ftp->expect(30, -re=>qr/name \(.*?\):\s*$/i) ) {
die "Never got username prompt on $host, ".$ftp->error( )."\n";
}
# 发送用户名数据
print "Sending username ($opts{u}) ... \n";
$ftp->send( "$opts{u}\r" );
# 等待密码输入提示
unless ( $ftp->expect( 30, -re=>qr/password:\s*$/i ) ) {
die "Never got password prompt on $hostname, ".$ftp->error( )."\n";
}
# 发送密码
print "Sending password ( $opts{p} ) ... \n";
$ftp->send( "$opts{p}\r" );
# 等待 ftp 命令行提示
unless ( $ftp->expect(30,"ftp>") ) {
die "Never got ftp prompt after sending username, ".$ftp->error( )."\n";
}
# 下载文件
foreach my $file ( @files ) {
print "Getting the $file ... \n";
$ftp->send( "get $file\r" );
unless ( $ftp->expect( 30,"ftp> " ) ) {
die "Never got ftp prompt after attempting to get $file, ".$ftp->error( )."\n";
}
}
# 断开 ftp 连接
print "Download finished. Disconnecting ... \n";
$ftp->send( "bye\r" );
$ftp->soft_close( );
print "Done.\n";
|
使用此脚本从 9.125.13.44 的 ftp 根目录下载 diskusage.log 文件,运行结果:
清单 23.ftpdemo.pl 执行结果
# ./ftpdemo.pl -u root -p zhu88jie 9.125.13.44 diskusage.log
Starting ftp session with server 9.125.13.44 ...
Sending username (root) ...
Sending password (zhu88jie) ...
Getting the diskusage.log ...
Download finished. Disconnecting ...
Done.
#
|
Expect::Simple 模块使用示例
本例演示如何使用 Expect::Simple 对目标程序的输入输出进行测试。脚本 target.pl 作为此例中的目标程序。target.pl 循环读取用户输入,直到用户输入 quit 时退出。
清单 24. target.pl
#! /usr/bin/perl
my $p = 'tpt %d> '; # 输入提示符
printf( $p, 0 );
while( <> ){
do { print "byebye\n"; last } if /quit/;
# 如果用户输入 quit 则输出"byebye",跳出循环
print uc( $_ ); # 否则以大写的形式输出用户本次输入
printf( $p, $. ); # 更新输入提示符,$. 为读入行数计数器
}
print "quit> \n"; # 打印结束输入提示符
|
某次运行结果如下:
清单 25. target.pl执行结果示例
# ./target.pl
tpt 0> 1
1
tpt 1> 2
2
tpt 2> quit
byebye
quit>
#
|
exptest.pl 脚本用来模拟用户输入不同数据以测试目标程序的功能:
清单 26.exptest.pl
#! /usr/bin/perl
use Test::More tests => 4; # 计划执行的测试用例总数
use Expect::Simple;
my %attr = ( Cmd => "./target.pl"
DisconnectCmd => 'quit',
Prompt => [ -re => 'tpt\s\d+> ', 'quit> '],
RawPty => 1,
Verbose =>3,
);
my $res; # 实际输出结果
my $obj = Expect::Simple->new( \%attr );
# 测试用例 1,用户输入 a,预期目标程序输出 A
$obj->send( 'a' );
chomp( $res = $obj->before );
is( $res, 'A', 'Test case 1: Input "a" -> Output "A"' );
# 测试用例 2,用户输入 b,预期目标程序输出 B
$obj->send( 'b' );
chomp( $res = $obj->before );
is( $res, 'B', 'Test case 2: Input "b" -> Output "B"' );
# 测试用例 3,用户输入 quit,预期目标程序输出 byebye
$obj->send( 'quit' );
chomp( $res = $obj->before );
is( $res, 'byebye', 'Test case 3: Input "quit" -> Output "byebye"' );
# 测试用例 4,目标程序输出 byebye 后应直接输出 quit>
is ( $obj->match_str, 'quit> ', 'Test case 4: Output "byebye" -> Output "quit>" ' );
|
程序引用了 Test::More 模块,Test::More 是一个编写测试脚本的框架,这里只介绍程序中涉及的部分,更多内容请查阅 CPAN 网站上相关信息。整个测试过程使用 is 函数来判断用例的执行结果:is ( $got, $expected, $comments )。该函数判断 $got与 $expected 是否相等,相等则通过,输出“ok”,否则输出“not ok”。
在target.pl 循环执行的过程中,如果用户未输入 quit,则每次 expect 都匹配到输入提示符“tpt\s\d+>”上,而终端回显又被关闭,因此匹配缓冲区的 before 部分即为目标程序上次输出的结果。程序中使用 $res 变量获得目标程序的实际输出,与预期结果比较判断测试用例是否通过。exptest.pl 实际执行的结果为:
清单 27.exptest.pl执行结果
# ./exptest.pl
1..4
Running command...done.
Sending `a'
ok 1 - Test case 1: Input "a" -> Output "A"
Sending `b'
ok 2 - Test case 2: Input "b" -> Output "B"
Sending `quit'
ok 3 - Test case 3: Input "quit" -> Output "byebye"
ok 4 - Test case 4: Output "byebye" -> Output "quit>"
Disconnecting.
#
|
转自:http://blog.csdn.net/asx20042005/article/details/7088821
Expect模块可以用来向需要交互执行的程序输入必要的信息。相当于expect脚本的Perl语言实现。
spawn函数——对应expect脚本中的spawn
语法:Expect->spawn($cmd,@param);
参数:LIST或program LIST形式。
功能:调用exec命令执行程序。
返回值:对象本身。
expect函数——对应expect脚本中的expect
语法:Expect::expect($timeout, -i => \@exp_list, @patternlist, -i => $exp, @pattern_list, ...);
or $exp->expect($timeout, @patternlist, -i => \@exp_list, @patternlist, -i => $exp, @pattern_list, ...);
参数:$timeout是超时时间,Patterns的形式是[ $pattern_type, $pattern, $sub, @subparms ],pattern_type的形式'-re'时是(RegExp, default),'-ex'时是(exact),sub是函数名,将按&{$sub}($exp, @subparms)形式来调用。
功能:捕获程序输出。
返回值:数组。
用法举例:
1.$object->expect(15, 'match me exactly','-re','match\s+me\s+exactly');
2.$telnet->expect(10,'-re',$match);
3.$exp->expect($timeout,
[ qr/username: /i, sub { my $self = shift;$self->send("$username\n");exp_continue; }],
[ qr/password: /i, sub { my $self = shift;$self->send("$password\n");exp_continue; }],
$shell_prompt);
send函数——对应expect脚本中的send
有时不是特别好用,推荐send_slow函数。
send_slow函数
语法:$exp->send_slow($delay,String);
参数:delay是延时时间,第二个参数是要输出的字符串。
功能:获得期待的字符串后,等待指定的时间,向程序输出预先指定的字符串。
返回值:无。
附:控制字符Ctrl的输入方法是\c,后面接其他字符。
interact函数——对应expect脚本中的interact
语法:$exp->interact([$in_handle],[$escape sequence])
参数:第一个参数是输入的文件句柄,第二个参数是逃逸的列表
功能:退出expect,调用程序继续执行。
返回值:无
soft_close函数
语法:$exp->soft_close();
参数:不接收参数。
功能:结束调用程序的运行。
返回值:程序退出时的状态。
hard_close函数
语法:$exp->hard_close();
参数:不接收参数。
功能:彻底的结束程序运行。
返回值:程序退出时的状态。
用法举例:
my $exp = Expect->spawn($cmd,@param) or die "Cannot spawn $cmd\n";
$exp->expect($timeout, -re=>'Enter passphrase:');
$exp->send_slow($delay,"$pass\r");
$exp->soft_close();
$exp->hard_close();
增加一例,不记得哪里转的了:
1 #!/usr/bin/perl -w
2 use strict;
3
4 use Expect;
5 use Getopt::Std;
6 # setup env
7 $Expect::Log_Stdout = 1;
8 $ENV{TERM} = "xterm";
9 my $prog = "telnet";
10 # parse commandline parameter.
11 my %optval;
12
13 getopts("h:u:p:",\%optval);
14 my $host = "10.11.105.188";
15 my $user = "root";
16 my $pass = "sigma-rt";
17 $host = $optval{h} if (defined($optval{h}));
18 $user = $optval{u} if (defined($optval{u}));
19 $pass = $optval{p} if (defined($optval{p}));
20
21 # initialize a expect object
22 our $exp = Expect->new();
23 # spawn the program
24 $exp = Expect->spawn($prog,"$host");
25 # specify the logfile.
26 $exp->log_file("out.log","w");
27 # the pattern of prompt
28 #my $prompt = '[\[\$\#\>]\s*$';
29 my $prompt = '[\#\$]\s*$';
30
31 my @cmdList = ();
32 @cmdList = ( @cmdList,"show vlan");
33 @cmdList = ( @cmdList,"show run");
34 @cmdList = ( @cmdList,"exit");
35
36 $exp->expect(2,
37 [qr/assword:/i => sub {$exp->send($pass."\n"); exp_continue;}],
38 [qr/user/i => sub {$exp->send($user."\n"); exp_continue;}],
39 [qr/login/i => sub {$exp->send($user."\n"); exp_continue;}],
40 [qr/yes\/no/i => sub {$exp->send("yes\n"); exp_continue;}],
41 [qr/$prompt/ => sub {$exp->send("\n");}],
42 );
43
44 # execute every command
45 foreach (@cmdList) {
46 $exp->send($_."\n") if ($exp->expect(8,
47 [qr/more/i => sub {$exp->send(" "); exp_continue;}],
48 [qr/$prompt/ => sub {$exp->send("\n"); }],
49 )
50 );
51 print "-----------------------------\n";
52 print $_."\n";
53 print "-----------------------------\n";
54 sleep 1;
55 next;
56 }
57 $exp->send("\n") if ($exp->expect(undef, '#')); |