対話的なプログラムとのやりとりを自動化するプログラム, バージョン 5
cmds
]
[
- [ f | b ]
]
cmdfile
]
[
args
]
send/expect
の概念に由来する。
(kermitや他のモデム制御プログラムでも、この概念は使われている)
しかし、uucp とは違って
Expect
は一般化されているので、想像されるどんなプログラムやタスクに対しても
マクロコマンド(user-level command)として機能できる。
Expect
は、同時に複数のプログラムと会話することができる。
Expect
にできることの例をいくつか挙げておく:
bu
bu
bu
bu
bu
cmdfile
を読み込み、実行するコマンドのリストを得る。
Expect
は、#! 表記をサポートする OS で、先頭行に
args
は、リストに変換されて、変数
argv
に保存される。
argc
は、argv のリスト長(要素の数)に設定される。
argv0
は、スクリプト名に設定される(スクリプトを使っていなければ、
バイナリの名前になる)。
例えば、以下のスクリプトを実行すると、スクリプト名と最初の引数3つを表示する:
send_user $argv0 [lrange $argv 0 2]\\n
Tcl
(Tool Command Language)を使用している。
Tcl は、制御フロー(例えば if, for, break)、式評価、および、
再帰やプロシジャ定義等他のいくつかの機能を提供する。
ここで使われているのに定義がないコマンド(例えば、
set ,
if ,
exec )
は、Tcl コマンドである。(tcl(3)を参照)。
Expect
は、以下に記述する追加コマンドをサポートする。
記述がない場合は、そのコマンドは空文字列を返す。
コマンドはアルファベットの列なのですぐにわかると思うが、
新しいユーザーは、
spawn ,
send ,
expect ,
interact ,
が、この順で並んでいるところを読み始めた方が理解しやすいと
気がつくかも知れない。
close [-slave] [-onexec 0|1] [-i spawn_id]
debug [[-now] 0|1]
disconnect
以下のスクリプトは、パスワードを読んで、一時間毎にパスワードを要求する プログラムを実行する。スクリプトはパスワードを読み込んでいるので、 タイプするのは一回だけで済む。 (パスワードのエコーを避ける方法については、 stty コマンドを参照)
if {[fork]!=0} exit disconnect
send_user password?\ expect_user -re (.*)\\n for {} 1 {} { if {[fork]!=0} {sleep 3600;continue} disconnect spawn priv_prog expect Password: send $expect_out(1,string)\\r exit }シェルの非同期実行(&)時に disconnect を用いる利点は、 Expect が disconnect の前に端末情報を保存しておいて、後で新しい pty にそのパラメタを 適用できる点にある。 & を使っていて Expect が制御を受けとって disconnect されてしまうと、端末情報を読み込むことはできない。
exit [-opts] [status]
status
(指定がないときは、0 )は、
Expect
の、終了ステータスとしてシステムに返される。
exit
は、スクリプトの終りに達すると、書いていなくても実行される、
exp_continue [-continue_timer]
-continue_timer
フラグはタイマーを再実行しないようにする。
(より詳細な情報は
expect
を参照のこと。)
exp_internal [-f file] value
value
がゼロでなければ、以降のコマンドの診断情報を
Expect
内部の stderr に送るようになる。
value
に0を指定するとこの出力は止まる。この診断情報には、受けとった
すべての文字と、現在の出力とパターンをマッチさせる全試行が
含まれる。
file
オプションを指定すると、すべての通常および診断出力がそのファイルに
出力される。(
value
の値とは無関係に)。すでにオープンされている診断出力ファイルは、
すべてクローズされる。
exp_open [args] [-i spawn_id]
exp_pid [-i spawn_id]
exp_send
exp_send_error
exp_send_log
exp_send_tty
exp_send_user
exp_version [[-exit] version]
expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
4番めのパターンにはスペースが含まれているのでクオートが必要である。 アクションとパターンを分離するセパレータでないことを指示する必要がある。 (3番めと4番めの)ように同じアクションを持つリクエストも並べて書く必要が ある。これは、正規表現パターンを用いることで回避できる(下記参照)。 グロブスタイルパターンについてもっと情報が欲しければ、Tcl のマニュアルを 読むこと。 正規表現パターンは、Tcl の regexp (regular expressionの短縮)コマンドで定義される文法に従う。 regexpパターンは、 -re フラグで始める。 前の例を、regexp で書き直すと、こうなる。:
\w' expect 'u +\w'invalid password 'u expect { busy {puts busy\\n ; exp_continue} failed abort invalid password abort timeout abort connected }
どちらのパターンのタイプも、固定されていない。どういう意味かというと、 文字列全体にマッチする必要はなくて、文字列のどこでもマッチすれば 良いということである。^ が先頭にマッチする。 $ が末尾にマッチする。 文字列の末尾にマッチさせなければ、spawn されたプロセスからエコーされた 文字列の途中で切り上げてレスポンスを返せることに注意すること。 正しく処理が実行されていても、出力は不自然に見える可能性がある。 それで、文字列の終りの文字を正確に記述できるなら、$ を使うことを勧める。
\w' expect 'u +\w'connected 'u expect { busy {puts busy\\n ; exp_continue} -re failed|invalid password abort timeout abort connected }
patlist
に
full_buffer
を指定すると、
match_max
バイト以上のデータを受けてパターンマッチしなかったときに、処理が実行される。
full_buffer
キーワードの有無に関わらず、忘れられたデータは
expect_out(buffer)
に保存される。
patlist
に、キーワード
null
を指定すると、ヌル文字が許可され
(
remove_nulls
コマンドを通して)、ヌル文字(ASCII 0)にマッチする。
glob や regexp では 0 バイトにマッチすることができない。
expect_out(1,string)
から
expect_out(9,string)
に保存される。
-indices
フラグをパターンの前で指定すると、マッチした部分文字列の
開始位置と終了位置が(
lrange
の引数として使える形で)、変数
expect_out(X,start)
と
expect_out(X,end)
に保存される。
X は数字で 0 〜 9 まで。
0 はパターン全体がマッチした部分を指示する。
例えば、プロセスがabcdefgh\\nを出力し、以下の形:
で受けると、以下の文を実行したのと同じ結果となる。
expect cd
この時、efgh\\nは出力バッファに残る。 プロセスがabbbcabkkkka\\nを出力し、以下の形:
set expect_out(0,string) cd set expect_out(buffer) abcd
で受けると、以下の文を実行したのと同じ結果になる。
expect -indices -re b(b*).*(k+)
この時、a\\nは出力バッファに残る。 パターン* (と -re .*)は、プロセスからのデータがさらに来ない限り、 出力バッファをフラッシュしない。 通常、マッチした出力は Expect の内部バッファから、切り捨てらる。 この動作は、 -notransfer フラグで抑止することができる。このフラグは、スクリプトを試している時に 役に立つ(そして、-notと略記しても良い)。
set expect_out(0,start) 1 set expect_out(0,end) 10 set expect_out(0,string) bbbcabkkkk set expect_out(1,start) 2 set expect_out(1,end) 3 set expect_out(1,string) bb set expect_out(2,start) 10 set expect_out(2,end) 10 set expect_out(2,string) k set expect_out(buffer) abbbcabkkkk
大域変数
expect { -i $proc2 busy {puts busy\\n ; exp_continue} -re failed|invalid password abort timeout abort connected }
any_spawn_id
の値は、
今の
expect
コマンド内で
-i
フラグを指示した spawn_id の全てにマッチさせるために使われる。
-i
フラグをパターンなしで指定すると(すなわち、別の
-i
が直後に続くと)、
any_spawn_id
で指定された、同じ
expect
コマンド内の他のパターンに対して、有効になる。
例えば、以下の断片は既に自動化されているユーザーガイドへのやりとりを 補助する。 この場合、端末は raw モードになる。 ユーザーが'+'を押すと変数がインクリメントされる。 pが押されると、プロセスへ復帰情報が送られる。 おそらくは同じようにiが押されると、スクリプトから制御を奪い、 ユーザーからの制御が行なえる。 どの場合も exp_continue コマンドが、今の expect に、処理を行なわせた後再びパターンマッチさせている。
expect { Password: { stty -echo send_user password (for $user) on $host: expect_user -re (.*)\\n send_user \\n send $expect_out(1,string)\\r stty echo exp_continue } incorrect { send_user invalid password or account\\n exit } timeout { send_user connection to $host timed out\\n exit } eof { send_user \\ connection to host failed: $expect_out(buffer) exit } -re $prompt }
デフォルトでは、 exp_continue は、タイムアウトタイマーをリセットする。 タイマを再開させるには、 exp_continue コマンドに -continue_timer フラグをつける。
stty raw -echo expect_after { -i $user_spawn_id p {send \\r\\r\\r; exp_continue} + {incr foo; exp_continue} i {interact; exp_continue} quit exit }
expect_after [expect_args]
expect_background [expect_args]
expect_before [expect_args]
たった一つの spawn id 指定だけが許される。-indirect フラグで、直接 spawn id を抑止し、間接的な指定から得られるidを指示する。
expect_before -info -i $proc
expect_tty [expect_args]
expect_user [expect_args]
fork
interact [string1 body1] ... [stringn [bodyn]]
string と body の組で、string が引数として並べられた順に比較される。 部分的にマッチした文字列は、残りが到着するまで送られて来ない。 何文字かさらに打ち込まれて、マッチが可能になると、今のマッチを判断する ためにだけ使われて他のマッチを始めることはしない。それゆえ、部分的に マッチしている文字列のマッチが完了するのは遅れることがある。 部分的にはマッチするが最終的にはマッチしない文字列の場合などである。 デフォルトでは、ワイルドカードを含まないマッチは、exactとなる。 ( expect コマンドがデフォルトでグロブスタイルのパターンを用いるのとは対照的に。) -ex フラグは、パターンをプロテクトするのに使える。 interact フラグがそうするように。 パターンが-で始まる場合、この方法で保護できる。 (-で始まる文字列は全て将来のオプションとして予約されている。)
\w' interact 'u +\w'$CTRLZ 'u +\w'{'u set CTRLZ \\032 interact { -reset $CTRLZ {exec kill -STOP [pid]} \\001 {send_user you typed a control-A\\n; send \\001 } $ {send_user The date is [exec date].} \\003 exit foo {send_user bar} ~~ }
interact_out
に保存される。
expect
が、その出力を変数
expect_out
に保存するのと似たようなものである。
-indices
フラグも同じようにサポートされる。
interact -input $user_spawn_id timeout 3600 return -output \\ $spawn_id
interact_out(spawn_id)
にパターン(あるいはeof)にマッチした spawn_id が設定される。
-nobuffer フラグは、文字が読まれる度に、その文字をマッチへ送る。
interact { -echo ~g {getcurdirectory 1} -echo ~l {getcurdirectory 0} -echo ~p {putcurdirectory} }
interact の間、前に使った log_user は無視される。特に、 interact は、その出力を記録される(標準出力に送られる)。 というのは、ユーザーはエコーバックのない状態でキーを打ちたくはない だろうと考えるからである。 -o フラグは、現プロセスの出力に key body ペアの key を結びつける。 こんな場合に便利である。例えば、telnet セッション中に望まない文字を 送ってくるホストを扱う場合である。 デフォルトでは、 interact は、ユーザーが Expect プロセス自身の標準入力に書き込み、標準出力を見ていると思っている。 -u フラグ(userのu)は interact に、引数で付けられた名前(spawned id である)のプロセスをユーザーとして 扱う。 これにより、変なループなしに2つの無関係なプロセスを結合させることが できる。デバッグする時の助けとして、Expect は常に診断結果を stderr へ送る。(ある種のログとデバッグ情報は stdout に送られる)。 同じ理由で、 interpreter コマンドは、stdin からデータを読む。 例えば、以下の断片はログインプロセスを作る。そして、(表示されない) ユーザーにダイアルし、両方の接続を行なう。 もちろん、loginをどんなプロセスに変えても良い。例えば、シェルなら、ア カウントとパスワードを与えなくてもユーザーが起動できる。
proc lognumber {} { interact -nobuffer -re (.*)\\r return puts $log [exec date]: dialed $interact_out(1,string) }
interact -nobuffer atd lognumber
複数のプロセスへの出力を行なうため、 -output フラグを前につけた各 spawn id のセットがリストされる。 出力 spawn id の組への入力は、 -input フラグによって決定される。 ( -input と -output フラグは両方とも expect コマンドの -i フラグと同じ書式である。( interact 内の any_spawn_id は意味がない点を除く。) 以下のフラグと文字列(あるいはパターン)は全て、別の -input フラグが現れるまで この入力を適用する。 -input が現れなかった場合、 -output は -input $user_spawn_id -output を行なう。 ( -input を持たないパターンも同様である。) -input が一つだけ指示されると、$user_spawn_id はその値で置き換わる。 二つめの -input が指示されると、$spawn_id が置き換わる。 以降の -input フラグも指定できる。
spawn login set login $spawn_id spawn tip modem # dial back out to user # connect user to login interact -u $login
interpreter [args]
log_file [args] [[-a] file]
追記
する。
ログオフや複数のログを書く時に都合が良いように。
ファイルを消して書き直す時は
-noappend
フラグを使う。
log_user -info|0|1
match_max [-d] [-i spawn_id] [size]
size
がないと、現在のサイズを復帰する。
-d
フラグを指示すると、デフォルトサイズが設定される。(初期状態の
デフォルト値は 2000。)
-i
フラグを指示すると、名前つき spawn id に対してサイズが設定される。
指定しなければ、カレントプロセスに対して設定される。
overlay [-# spawn_id] [-# spawn_id] [...] program [args]
これは、 interact -u とするよりも効果的である。しかし、 Expect プロセスが制御していないのでプログラム能力が犠牲となる。 制御されない端末ができてしまうことに注意すること。それで、 disconnect するか標準入力をリマップするとジョブ制御プログラム (シェル、ログインなど)が正しく機能しない。
overlay -0 $spawn_id -1 $spawn_id -2 $spawn_id chess
parity [-d] [-i spawn_id] [value]
value
が 0 であれば、パリティは取り除かれる。
それ以外の場合、取り除かれない。
value
が指定されない場合、現在の値が復帰する。
-d
フラグは、パリティのデフォルト値を設定する。(イニシャル時のデフォルト値は
1 である。すなわち、パリティが取り除かれる。)
-i
フラグを指示すると、パリティの値が引数の名前つきの spawn id に対して
設定される。引数がなければ現在のプロセスに対して設定される。
remove_nulls [-d] [-i spawn_id] [value]
expect_out
か
interact_out
に保存されている spawn されたプロセスの出力からヌルを保持するか取り除くかを
設定する。
value
が 1 なら、ヌルは取り除かれる。もし、
value
が 0 なら、ヌルは取り除かれる。
value
がなければ、現在の値が復帰する。
-d
フラグは、デフォルト値を設定する。(イニシャルのデフォルト値は、
1 である。それゆえ、ヌルは取り除かれる。)
-i
フラグは、名前つきの spawn id に対して値を設定する。なければ、
現プロセスに対して設定する。
send [-flags] string
は、文字 h e l l o <blank> w o r l d <return> を現在のプロセスに 送る。 (Tcl は、printf に似たコマンド ( format と呼ばれる )を持っていて、複雑な文字列を組み立てることができる。) 文字は直ちに送られる。ただし、入力にラインバッファのあるプログラムでは、 リターンコードが送られるまで文字が読まれない。リターンコードは、 \\rと表記する。
send hello world\\r
ハングさせてしまった後は、以下のようにした方が良いだろう。:
set send_human {.1 .3 1 .05 2} send -h I'm hungry. Let's do lunch.
send にエラーや修正を埋め込んであっても、 エラーはシミュレートされない点に注意すること。
set send_human {.4 .4 .2 .5 100} send -h Goodd party lash night!
string
の引数が、ヌル文字あるいはブレークを送るフラグとして指定できる。
exp_send は send のエイリアスである。あなたが Expectk か、Tk 環境で動く Expect の他の変種を 使っている場合、 send は、全く異なった目的のために使われている。 exp_send が、両環境の間での互換性のために提供されている。 似たようなエイリアスが他の Expect の他の send コマンドのために提供されている。
# どのように破るかのヒントをハッカーに与えてしまわないように、 # このシステムでは外部のパスワードに対するプロンプトを提供しない。 # exec が完了するのを 5 秒待て。 spawn telnet very.secure.gov sleep 5 send password\\r
send_error [-flags] string
send_log [--] string
send_tty [-flags] string
send_user [-flags] string
sleep seconds
spawn [args] program [args]
spawn_id
には、そのプロセスへの参照を行なう識別子が設定される。
spawn_id
によって記述されるプロセスは
current process
が考慮される。
spawn_id
は、読んでも書いても良く、効果的なジョブ制御を提供する。
user_spawn_id
はユーザーを参照する識別子の入ったグローバル変数である。
例えば、
spawn_id
が、この値に設定された場合、
expect
は、
expect_user
のような動きをする。
error_spawn_id
は、標準エラー出力を参照する識別子の入ったグローバル変数である。
例えば、
spawn_id
が、この値に設定された場合、
send
は、
send_error
のような動きをする。
tty_spawn_id
は、/dev/tty を参照する識別子の入ったグローバル変数である。
/dev/tty が存在しない(cron, at, バッチスクリプトの中)場合、
tty_spawn_id
は定義されない。以下のように確認することができる。:
spawn UNIX プロセス ID を復帰する。spawn されたプロセスがない場合、0 が 復帰する。変数
if {[info vars tty_spawn_id]} { # /dev/tty exists } else { # /dev/tty doesn't exist # probably in cron, batch, or at script }
spawn_out(slave,name)
は pty スレーブデバイスの名前に設定される。
デフォルトでは、
spawn
はコマンド名と引数をエコーする。
-noecho
フラグで
spawn
がこうするのを止められる。
-console
フラグは、コンソールへの出力を起こし、spawn されたプロセスへの
リダイレクトされる。この機能は未サポートのシステムがある。
stty_init
が定義されていると、stty の引数の形式を解釈できるので、より詳細な設定を
行なえる。例えば、
set stty_init raw
は、以降 spawn されたプロセスの端末を raw モードで開始する。
-nottycopy
は、ユーザーの tty に基づいた初期化を飛ばす。
-nottyinit
は、正常な初期化を飛ばす。
普通、
spawn
は、実行するのにわずかの時間しかかからない。spawn に時間をかけたいので
あれば、おそらく割り込まれた pty に遭遇するだろう。たくさんのテストが
間違ったプロセスに掛かり合うことを避けることができる。
(割り込まれた pty につき、10 秒かかる。)
-d
オプションをつけて Expect を走らせると、
Expect
がおかしな状態のたくさんの pty に遭遇しているかどうかが表示される。
これらの pty がつながっているためにプロセスを殺せない場合、リブートするしか
頼れる復旧手段はない。
program
が spawn に成功しなかった場合
(例えば、
program
がなかった場合など)、エラーメッセージが次の
interact
か
expect
コマンドで復帰する。つまり。
program
が、出力としてエラーメッセージを出しているように見せる。
この動作は、
spawn
の実装の自然な帰結である。内部で、spawn がフォークされ、その後、
spawn されたプロセスがオリジナルの
Expect
プロセスとその spawn_id で会話を行なう。
spawn_out(slave,fd)
には、pty スレーブとつながっているファイル識別子が設定される。
close -slave で close できる。
strace level
level
は、トレースへの呼び出しスタックの深さを示す。
例えば、
以下のコマンドは
Expect
最初の 4 レベルの呼び出しをトレースする。
それ以上の深さはトレースしない。
expect -c strace 4 script.exp
stty args
stty -echo send_user Password: expect_user -re (.*)\\n set password $expect_out(1,string) stty echo
system args
args
を、sh(1)に入力する。端末からコマンドを叩くのとちょうど同じである。
Expect
は、シェルが終るのを待つ。sh からの復帰値は、
exec
がその復帰値を扱うのと同じに扱われる。
exec
が、スクリプトに標準入出力をリダイレクトするのと対照的に、
対照的に
system
は、リダイレクションを行なわない。(他に文字列そのものでリダイレクトを
指示しない限り。) それで、/dev/tty と直接話さなければならないプログラムを
使うことができる。同じ理由で、
system
の結果は、ログに記録されない。
timestamp [args]
%a 略記された曜日の名前 %A 略されない曜日の名前 %b 略記された月の名前 %B 略されない月の名前 %c 次の形式で書かれた時刻: Wed Oct 6 11:45:56 1993 %d 日 (01-31) %H 時 (00-23) %I 時 (01-12) %j 日 (001-366) %m 月 (01-12) %M 分 (00-59) %p am または pm %S 秒 (00-61) %u 日 (1-7, 月曜日が週の最初の日) %U 週 (00-53, 最初の日曜日が第1週の最初の日) %V 週 (01-53, ISO 8601 スタイル) %w 日 (0-6) %W 週 (00-53, 最初の月曜日が第1週の最初の日) %x date-time as in: Wed Oct 6 1993 %X time as in: 23:59:59 %y year (00-99) %Y year as in: 1993 %Z timezone (or nothing if not determinable) %% a bare percent signこの他の % 指定は定義されていない。他の文字は変更されない。 C ロカールだけがサポートされる。
trap [[command] signals]
command
を実行する。
このコマンドは、グローバルスコープで実行される。
もし、
command
が指定されなければ、シグナルアクションが復帰する。
command
が、文字列 SIG_IGN であれば、シグナルが無視される。
command
が、文字列 SIG_DFL であれば、シグナルはデフォルトの動きをする。
signals
は、シグナルが1つでも複数のシグナルのリストでも良い。シグナルは、
数字とsignal(3)に記述されている文字列のどちらで指定しても良い。
プレフィクスのSIGは、省略しても良い。
-Dフラグを使ってデバッガを起動するなら、SIGINT が再定義されてから対話型 デバッガが起動される。これは以下の trap によって起こる。
trap exit {SIGINT SIGTERM}
デバッガのトラップは、環境変数 EXPECT_DEBUG_INIT を設定して、新しく trap を起動することで変更できる。
trap {exp_debug 1} SIGINT
代わりに、別のシグナルを使ってデバッガに割り込みをかけることができる。
if ![exp_debug] {trap mystuff SIGINT}
wait [args]
vgrind -lexpect file
spawn_id
はスコープのため、もはや存在しないので spawn されたプロセスは単純には
アクセス不能となる。そのようなプロセスには、global spawn_idを
付け加えねばならない。
set env(TERM) vt100
set env(SHELL) /bin/sh set env(HOME) /usr/local/bin
のような Expect プログラムは失敗する。失敗しないように非対話的なプログラムでは spawn せずに exec すること。こういう状況は考えられるが、実際には私はまだその状態に 陥ったことがない。つまり、この原因で対話プログラムの最後の出力を 取りこぼすという状態になったことがない。
spawn date sleep 20 expect
send speed 9600\\r; sleep 1 expect { timeout {send \\r; exp_continue} $prompt }
私としては、見えると思っているものの終りの部分を含んだ expect パターンを書くように勧める。そうすれば、全体を見る前に応答を返してしまう ことを避けることができる。さらに、全体が見える前に答えることもできるが その文字は質問に混ざって echo される。言い替えれば、会話は正常だが見た目は 混ざって見える。
set prompt (%|#|\\\\$) $ ;# default prompt catch {set prompt $env(EXPECT_PROMPT)}
expect -re $prompt