対話的なプログラムを自動化するのに expect があるという話を前に同僚から聞いていましたが、Perl でやるとどうなるんだろうとか思ってると、そのまま Expect っていうモジュールがあった。
インタラクティブなコマンドのクソ面白くない例としてパスワード変更がありますが、それを Expect.pm で書くとたぶんこんな感じになるんだと思います。
#!/usr/bin/perl use strict; use warnings; use Expect; my $cmd = 'passwd'; my $oldpass = 'aaaa'; my $newpass = 'bbbb'; my $expect = Expect->new; $expect->log_stdout(0); # hide stdout of 'passwd' $expect->log_file('./passwd.log', 'w'); # logging $expect->spawn( $cmd ) or die "cannot spawn '$cmd'"; $expect->expect( undef, [ 'Old Password:' => sub { shift->send( "$oldpass\n" ); exp_continue; } ], [ 'New Password:' => sub { shift->send( "$newpass\n" ); exp_continue; } ], [ 'Retype New Password:' => sub { shift->send( "$newpass\n" ); } ], [ eof => sub {} ], );
expect に渡している ARRAYREF は、expect コマンドより多少直感的なように感じます。ちなみにこの ARRAYREF でキーとして使っているもの ('Old Password:' など)は、qr// の正規表現で代替できます。
# 上記のようにリテラルの形で指定しても、内部で正規表現として評価されているっぽい。
これ系のプログラムってデバッグが基本的に大変になりますが、Expect には exp_internal っていうメソッドが用意されていて、これを呼び出すことで Expect が内部で何をしているかを簡単に確認することができます。
実際に、上記の例で $expect->exp_internal(1) とすると、以下のような出力が確認できます。
Changing password for kiririmode. spawn id(3): Does `Changing password for kiririmode.\r\n' match: pattern #1: -re `Old Password:'? No. pattern #2: -re `New Password:'? No. pattern #3: -re `Retype New Password:'? No. pattern #4: -eof `'? No.
ここでは、"Changing password for kiririmode." という文字列に対して何もヒットしてません。
Old Password: spawn id(3): Does `Changing password for kiririmode.\r\nOld Password:' match: pattern #1: -re `Old Password:'? YES!! Before match string: `Changing password for kiririmode.\r\n' Match string: `Old Password:' After match string: `' Matchlist: () Calling hook CODE(0x1008274c0)... Sending 'aaaa\n' to spawn id(3) at /System/Library/Perl/Extras/5.10.0/Expect.pm line 1264 Expect::print('Expect=GLOB(0x10047bf10)', 'aaaa\x{a}') called at ./expect.pl line 17 main::__ANON__('Expect=GLOB(0x10047bf10)') called at /System/Library/Perl/Extras/5.10.0/Expect.pm line 760 Expect::_multi_expect(undef, undef, 'ARRAY(0x100937c90)') called at /System/Library/Perl/Extras/5.10.0/Expect.pm line 565 Expect::expect('Expect=GLOB(0x10047bf10)', undef, 'ARRAY(0x100937470)', 'ARRAY(0x1009375c0)', 'ARRAY(0x100820448)', 'ARRAY(0x100827c40)') called at ./expect.pl line 24 Continuing expect, restarting timeout...
ここでは、"Old Password:" という文字列にヒットしているので、"aaaa\n" が passwd に渡されていることが確認できます。