EXPECT(1) | FreeBSD General Commands Manual | EXPECT(1) |
名前
expect -対話的なプログラムとのやりとりを自動化するプログラム, バージョン 5書式
expect [ -dDinN ] [ -c cmds ] [ -[ f| b] ] cmdfile ] [ args ]イントロダクション
Expect は、スクリプトの指示に従って、対話的なプログラムと"会話"するプログラムである。以下のスクリプトに示すように、 Expect には、対話プログラムからの期待されうる入力とそれに対する正しい応答を教えておく。インタプリタは分岐処理と高度な制御構造を提供し、対話プログラムへの指示を行なう。さらに、必要な時にスクリプトから制御を奪って直接人間が指示を行ない、その後、制御をスクリプトへ戻すことができる。Expectk は Expect と Tk の混合物である。 Expect であり、かつ、 Tk's wish であるかのように振舞う。 Expect は、C あるいは C++ (つまり、Tcl 以外)から、直接使うこともできる。 libexpect(3)を参照。
"Expect"という名前は、uucpで有名になった send/expect の概念に由来する。 (kermitや他のモデム制御プログラムでも、この概念は使われている) しかし、uucp とは違って Expect は一般化されているので、想像されるどんなプログラムやタスクに対してもマクロコマンド(user-level command)として機能できる。 Expect は、同時に複数のプログラムと会話することができる。
Expect にできることの例をいくつか挙げておく:
- •
- 電話代を払わずにログインできるようにコンピュータからあなたに電話を掛け直させる。
- •
- ゲーム(例えば rogue)を始める時に、最適なパラメタがもらえなかった場合最適なパラメタがもらえるまで何度でもリスタートを行ない、その後制御を人間に移す。
- •
- fsckを走らせた時に現れる質問に、前もって決めておいた方針に従って、 "yes", "no", "手入力"を切替えて、返答する。
- •
- 他のネットワークや BBS(例えばMCI Mail, CompuServe)に接続した時に自動的にメールの取り込み、発信を行なう。
- •
- 環境変数、カレントディレクトリ、その他の情報を、rlogin, telnet, tip, su, chgrp などを行なった先へ持っていく。
これらの処理をシェルが行なえない理由はたくさんある (やってみればわかるだろう)。全部 Expect ならできる。
一般に Expect は、プログラムとユーザーのやりとりが必要なプログラムを走らせるときに役に立つ。大事なことは、このやりとりがプログラムの性格を持っているということである。 Expect は、必要ならユーザーに制御を返すこともできる(しかも、プログラムは中断されない)。同様に、ユーザーは制御をいつでもスクリプトに返すことができる。
用法
Expect は、 cmdfile を読み込み、実行するコマンドのリストを得る。 Expect は、#! 表記をサポートする OS で、先頭行に#!/usr/local/bin/expect -f
-d フラグで、デバッグ情報出力を有効にする。基本的に内部のコマンド(例えば expect や interact) の振舞いを報告する。このフラグは"exp_internal 1"とスクリプトの先頭に書くのと同じことだが、さらに Expect のバージョン番号も出力される。 ( strace コマンドは、命令をトレースするのに便利である。 trace コマンドは、変数値をトレースするのに便利である。) (Expectk を使う時は、このオプションは -diag と書く。)
-D フラグで対話型デバッガを有効にする。整数値が続かなければならない。値が 0 でないか、C が押されると、次の Tcl 処理の前でデバッガに制御が移る。ブレークポイントにかかった時や他のデバッグ命令を実行した時も、同じである。デバッガについての情報がもっと欲しければ、 README や下記の関連項目を読むこと。 (Expectk を使っている時は、このオプションは -Debug)
-f フラグで、コマンドを読んでくるファイルを指示する。このフラグはあってもなくても良いのだが、#! 表記と一緒に使うと引数からプログラム名を除けるから、その場合意味がある。 (Expectk では -file)
デフォルトでは、コマンドファイルがメモリに一度に読み込まれ、そこで実行される。一行ずつコマンドファイルを読んだ方が望ましい場合もある。例えば、標準入力はこのように扱った方が良い。強制的にこのモードを指定するには、 -b フラグを指定する。 (Expectkでは、 -buffer) stdio のバッファリングは依然有効であるが、FIFO または stdin から読み込む場合は問題は起こらないはずであることに注意すること。
"-"がファイル名として渡されると、標準入力からスクリプトが読み込まれる。 (本当に"-"と言うファイルが読みたければ、"./-"と書くこと)
-i フラグを指示すると、 Expect はファイルからスクリプトを読まずに対話用プロンプトを表示する。 exit コマンドか、EOF を受けるとシェルを終了する。詳細については interpreter (下記) を参照のこと。 -i を指定した場合には、コマンドファイルも -c も指示されなかったものとして実行される。 (Expectkでは -interactive)
-- は、オプションの終りを区切るのに用いられる(省略可能)。これは、あなたがオプションタイプの引数を Expect に解釈させないで、スクリプトにそのまま渡したい時に役に立つ。 #! 行にこれを置いて、Expect にオプションとして解釈させないようにすることができて便利た。
例えば、以下のように書くと(スクリプト名も含めて)オリジナルの引数は、変数 argv に残される。
#!/usr/local/bin/expect --
#!行に引数を加えると、通常の getopt(3) や execve(2) ではその引数が見えてしまうことに注意すること。
-N フラグを用いない限り、ファイル $exp_library/expect.rc が(あれば)自動的に実行される。 (Expectk では、 -NORC) -n フラグ (Expectk では、 -norc) を用いない限り、続いて、ファイル ~/.expect.rc が、自動的に実行される。環境変数 DOTDIR が定義されていれば、そこが .expect.rc のあるディレクトリとして扱われる。
この二つのrcファイルの実行より先に -c フラグで指示されたコマンドが実行される。
-v フラグを指示すると、バージョン番号を表示して終了する。 (Expectkでは、-version)
オプション args は、リストに変換されて、変数 argv に保存される。 argc は、argv のリスト長(要素の数)に設定される。
argv0 は、スクリプト名に設定される(スクリプトを使っていなければ、バイナリの名前になる)。例えば、以下のスクリプトを実行すると、スクリプト名と最初の引数3つを表示する:
send_user "$argv0 [lrange $argv 0 2]\n"
コマンド
Expect は、 Tcl (Tool Command Language)を使用している。 Tcl は、制御フロー(例えば if, for, break)、式評価、および、再帰やプロシジャ定義等他のいくつかの機能を提供する。ここで使われているのに定義がないコマンド(例えば、 set, if, exec) は、Tcl コマンドである。(tcl(3)を参照)。 Expect は、以下に記述する追加コマンドをサポートする。記述がない場合は、そのコマンドは空文字列を返す。コマンドはアルファベットの列なのですぐにわかると思うが、新しいユーザーは、 spawn, send, expect, interact, が、この順で並んでいるところを読み始めた方が理解しやすいと気がつくかも知れない。
この言語(Expect と Tcl の両方)へのイントロダクションとしては、 "Exploring Expect"という本(関連項目を参照)がベストである。このマニュアルページはリファレンスとして書いているので、含まれている例は非常に限られている。注意すること。
このマニュアルページで、大文字の"E"で"Expect"とあれば、それは、 Expect プログラムを指し、一方小文字の"e"で"expect"とあれば、それは、 Expect プログラムに実装されている expect コマンドを指す。注意すること。
- close [-slave] [-onexec 0|1] [-i spawn_id]
-
カレントプロセスへのコネクションをクローズする。ほとんどの対話型プログラムが標準入力の EOF を検出し exit する。それゆえ、
close はそのプロセスを kill するのにも通常充分である。
-i フラグを指示すると、続く spawn_id を持つプロセスをクローズする。
- debug [[-now] 0|1]
-
は、Tcl デバッガを制御する。デバッガにより、ステップ実行、ブレークポイントの設定などが行なえる。
- disconnect
- fork されたプロセスを端末から切り離す。バックグラウンドで動作は続く。プロセスは可能であれば自分自身のプロセスグループを与える。標準入出力は、/dev/null にリダイレクトされる。
-
以下の断片は、
disconnect を使って、バックグラウンドでスクリプトの実行を続ける。
if {[fork]!=0} exit
disconnect
. . .
以下のスクリプトは、パスワードを読んで、一時間毎にパスワードを要求するプログラムを実行する。スクリプトはパスワードを読み込んでいるので、タイプするのは一回だけで済む。 (パスワードのエコーを避ける方法については、 stty コマンドを参照)
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]
-
Expect を exit させるか、そのための準備を行なう。
- exp_continue [-continue_timer]
- exp_continue コマンドは expect 自身に待っていた値が来なかった時のように、expect の実行を続ける。デフォルトでは exp_continue は時間切れタイマーをリセットする。 -continue_timer フラグはタイマーを再実行しないようにする。 (より詳細な情報は expect を参照のこと。)
- exp_internal [-f file] value
- value がゼロでなければ、以降のコマンドの診断情報を Expect 内部の stderr に送るようになる。 value に0を指定するとこの出力は止まる。この診断情報には、受けとったすべての文字と、現在の出力とパターンをマッチさせる全試行が含まれる。
-
file オプションを指定すると、すべての通常および診断出力がそのファイルに出力される。(
value の値とは無関係に)。すでにオープンされている診断出力ファイルは、すべてクローズされる。
- exp_open [args] [-i spawn_id]
-
元の spawn id に結びつけられたファイル ID を返す。そのファイル ID は、Tcl の
open コマンドでオープンした時と同様に扱える。 (spawn id は、もう使われるべきでない。
wait も実行すべきではない。)
- exp_pid [-i spawn_id]
- 現在の spawn されたプロセスのプロセス ID を返す。 -i フラグを指示すると、与えられた spawn id に対するプロセスの ID を返す。
- exp_send
- send のエイリアス。
- exp_send_error
- send_error のエイリアス。
- exp_send_log
- send_log のエイリアス。
- exp_send_tty
- send_tty のエイリアス。
- exp_send_user
- send_user のエイリアス。
- exp_version [[-exit] version]
- は、スクリプトが現バージョンのExpectで動くことを確かめる時に役に立つ。
- 引数がなければ、 Expect の現在のバージョンを返す。このバージョンはあなたのスクリプト内で設定しても良い。あなたが最近のバージョンに入った機能を使わないのであれば、もっと前のバージョンを指定することができる。
- バージョンはドットで区切られた 3 つの番号である。最初の番号は、メジャー番号である。違うメジャー番号の Expect 用に書いたスクリプトは、まず動かない。 exp_version は、メジャー番号がマッチしないとエラーを返す。
- 2 番めの番号はマイナー番号である。使っている Expect よりマイナー番号がより大きい Expect 向けのスクリプトは、新機能の使用未使用によるが、動かないかも知れない。 exp_version はメジャー番号がマッチしてもマイナー番号が使っている Expect のバージョンより大きいとエラーを返す。
- 3番めの番号は、バージョン比較には使われない。しかし、文書の更新やプログラムの最適化が行なわれて、 Expect のディストリビューションが更新されると、番号が増えていく。新しいマイナーバージョンが設定される度に、0 にリセットされる。
- -exit フラグをつけると、バージョンが合わなかった時に Expect はエラーを表示し exit する。
- expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
- は、spawn されたプロセスの出力がパターンのどれかにマッチするか、指定された時間が経過するか、enf-of-file を見つけるか、のいずれかが成立するまでウェイトする。最後の body が空なら、それは省略できる。
- 一番最後に実行された expect_before コマンドのパターンが、どのパターンより先にチェックされる。一番最後に実行された expect_after コマンドのパターンが、どのパターンより後にチェックされる。
- expect 全体への引数が 1 行に収まらなかった場合は、引数を"ブレース"することで、各行の終りにバックスラッシュをつけるのを避けることができる。この場合、ブレースしたにもかかわらず通常の Tcl 展開が発生する。
- もし、パターンがキーワード eof であれば、end-of-file 発見時に処理が実行される。もし、パターンがキーワード timeout であれば、タイムアウトが発生した時に処理が実行される。 timeout キーワードが使われなかった場合、タイムアウト時にはなにもしない。デフォルトタイムアウトは 10 秒である。設定することもできる。例えば 30 秒と設定したければ、"set timeout 30"を実行すること。タイムアウトさせないためには、値-1 を設定する。もし、パターンがキーワード default であれば、タイムアウトか end-of-file のいずれかで処理が実行される。
- パターンにマッチすれば、処理は実行される。 expect は、行なった処理(関連するブレース内の処理)の結果を返す。 (パターンにマッチしなかった時は、空文字列を返す。) 複数のパターンにマッチした場合、最初にマッチしたパターンに対応する処理が実行される。
- 対話型プログラムからの新しい出力が Expect に届くたびに、リストされている順にパターンとの比較が行なわれる。それゆえ、マッチすべきものがないことを確認するために、プロンプトのように来ることがわかっているパターンを用意することができる。プロンプトがない場合には、(あなたが手で打つ時に判断しているように) timeout を用いなければならない。
-
パターンは 3 通りに書ける。デフォルトは、Tcl の
string match コマンドの書式である。(このパターンはグロブで参照される C-shell の正規表現に似ている。)
-gl フラグは、他のマッチからパターンを保護するのに使う。 "-"で始まるパターンは、この方法で保護すべきである。 ("-"で始まる文字列は将来の拡張で、オプションとして使われるかも知れないから)
-
例えば、以下の断片はログインの成功を監視する。 (
abort はスクリプトのどこか他の場所で定義されていると仮定している。注意すること。)
expect {
busy {puts busy\n ; exp_continue}
failed abort
"invalid password" abort
timeout abort
connected
}
4番めのパターンにはスペースが含まれているのでクオートが必要である。アクションとパターンを分離するセパレータでないことを指示する必要がある。 (3番めと4番めの)ように同じアクションを持つリクエストも並べて書く必要がある。これは、正規表現パターンを用いることで回避できる(下記参照)。グロブスタイルパターンについてもっと情報が欲しければ、Tcl のマニュアルを読むこと。
-
正規表現パターンは、Tcl の
regexp ("regular expression"の短縮)コマンドで定義される文法に従う。 regexpパターンは、
-re フラグで始める。前の例を、regexp で書き直すと、こうなる。:
expect {
busy {puts busy\n ; exp_continue}
-re "failed|invalid password" abort
timeout abort
connected
}
どちらのパターンのタイプも、"固定されていない"。どういう意味かというと、文字列全体にマッチする必要はなくて、文字列のどこでもマッチすれば良いということである。^ が先頭にマッチする。 $ が末尾にマッチする。文字列の末尾にマッチさせなければ、spawn されたプロセスからエコーされた文字列の途中で切り上げてレスポンスを返せることに注意すること。正しく処理が実行されていても、出力は不自然に見える可能性がある。それで、文字列の終りの文字を正確に記述できるなら、$ を使うことを勧める。多くのエディタでは、^ と $ は行頭、行末に正確にマッチする。しかし、expect は行指向ではないので、(行ではなく)データの始まりと終りにマッチする。 ("EXPECTヒント"内の、バッファリングの消化不良に関する部分を参照のこと)
-ex フラグは、"正確に(exact)"指示された文字列にマッチする。 * や ^ などの解釈は行なわれない。(ただし、通常の Tcl 展開は行なわれる)。 Exact パターンは常に固定されている。
- -nocase フラグは、小文字が含まれている場合に大文字に変換してからマッチさせる。パターンには影響しない。
-
出力を読んでいて、2000 バイトを超えてしまったデータは"忘れられる"。この動作は、
match_max 関数で変更できる。 (極端に大きな値はパターンマッチの性能を低下させることに注意すること。)
patlist に
full_buffer を指定すると、
match_max バイト以上のデータを受けてパターンマッチしなかったときに、処理が実行される。
full_buffer キーワードの有無に関わらず、忘れられたデータは expect_out(buffer) に保存される。
expect "cd"
で受けると、以下の文を実行したのと同じ結果となる。
set expect_out(0,string) cd
set expect_out(buffer) abcd
この時、"efgh\n"は出力バッファに残る。プロセスが"abbbcabkkkka\n"を出力し、以下の形:
expect -indices -re "b(b*).*(k+)"
で受けると、以下の文を実行したのと同じ結果になる。
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
この時、"a\n"は出力バッファに残る。パターン"*" (と -re ".*")は、プロセスからのデータがさらに来ない限り、出力バッファをフラッシュしない。
-
通常、マッチした出力は Expect の内部バッファから、切り捨てらる。この動作は、
-notransfer フラグで抑止することができる。このフラグは、スクリプトを試している時に役に立つ(そして、"-not"と略記しても良い)。
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 コマンド内の他のパターンに対して、有効になる。-i フラグには、グローバル変数の名前を指定することもできる。その場合、その変数は、spawn id のリストである。変数は変わるたびに読み直される。こうすることで、コマンドが実行されている間に I/O ソースを変更することができる。この方法で指定される spawn id を"間接(indirect)" spawn id と呼ぶ。
break や continue などのアクションは、制御構造(すなわち、 for, proc )内で通常通りの振舞いをする。
exp_continue コマンドは、 expect ループから抜けるような状況で実行を続けさせる。
-
ループを書いたり、expect コマンドを繰り返すことを避ける時に便利である。以下の例はログインを自動化するコードの断片である。
exp_continue によって、(再びプロンプトを探すための)2 つめの
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
}
例えば、以下の断片は既に自動化されているユーザーガイドへのやりとりを補助する。この場合、端末は raw モードになる。ユーザーが'+'を押すと変数がインクリメントされる。 "p"が押されると、プロセスへ復帰情報が送られる。おそらくは同じように"i"が押されると、スクリプトから制御を奪い、ユーザーからの制御が行なえる。どの場合も exp_continue コマンドが、今の expect に、処理を行なわせた後再びパターンマッチさせている。
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
}
- デフォルトでは、 exp_continue は、タイムアウトタイマーをリセットする。タイマを再開させるには、 exp_continue コマンドに -continue_timer フラグをつける。
- expect_after [expect_args]
- は、 expect_before と同様の動きをするが、 expect と expect_after の両方にマッチした場合、 expect のパターンが使用される点が異なる。より詳しい情報は、 expect_before コマンドの項を参照のこと。
- expect_background [expect_args]
-
は、
expect と同じ引数をとるが、その場で復帰する。パターンは新しいデータが届くたびにチェックされる。パターン
timeout と
default は、
expect_background には、意味がないし、無視される。
expect と同様に、
expect_background コマンドは
expect_before や
expect_after パターンを使える。
- expect_before [expect_args]
-
は
expect と同じ引数をとるが、その場で復帰する。もっとも最近、同じ spawn id に対して
expect_before で使われたパターン・アクションのペアが、続く
expect コマンドに対して使用される。パターンがマッチすると、
expect コマンドにマッチした時と同じように動作する。処理は、その
expect のコンテキストで行なわれる。
expect_before と
expect の両方のパターンにマッチした場合、
expect_before のパターンが使われる。
expect_before -info -i $proc
たった一つの spawn id 指定だけが許される。-indirect フラグで、直接 spawn id を抑止し、間接的な指定から得られるidを指示する。spawn id を指示する代わりに、"-all"フラグを使って、全ての spawn id に "-info"の報告をさせることができる。
-info フラグを使った時の出力結果は、expect_before への引数として再利用できる。
- expect_tty [expect_args]
- は、 expect と似た動きをするが、文字列を /dev/tty (すなわち、ユーザーからのキー入力) から読み込む。デフォルトでは、cooked mode で読み込まれるので、行はリターンで終らなければならない。そうしないと expect が読めない。この動きは、 stty を使って変えられる。 (下の stty コマンドを参照)
- expect_user [expect_args]
- は expect と似た動きをするが、文字列を stdin(すなわち、ユーザーからのキー入力) から読み込む。デフォルトでは、cooked mode で読み込まれるので、行はリターンで終らなければならない。そうしないと expect が読めない。この動きは、 stty を使って変えられる。 (下の stty コマンドを参照)
- fork
- は、新しいプロセスを作る。この新しいプロセスは、現在の Expect プロセスの正確なコピーである。成功すると fork は新しい(子)プロセスに 0 を返し、親プロセスに子プロセスのプロセスIDを返す。失敗する(スワップ、メモリなどのリソース不足か?)と、 fork は、親プロセスに-1 を返す。新しい子プロセスは作成されない。
- フォークされたプロセスは、 exit コマンドで ext できる。元のプロセスと同様である。フォークされたプロセスはログファイルを作っても良い。多くのプロセスでデバッグもログもできなければ、結果、混乱するだけである。
- pty のインプリメンテーションの中には、複数の読み手と書き手が一瞬でもあれば、混乱するものがある。それで、プロセスを spawn する前には fork しておくのが一番安全である。
- interact [string1 body1] ... [stringn [bodyn]]
- は、現プロセスの制御をユーザーに渡す。結果、現プロセスに送られたキーストロークと現プロセスの標準出力と標準エラー出力が復帰する。
- string と body の組が、引数として指示できる。(デフォルトでは、文字列は現プロセスには送られない) 最後の body がないと、 interpreter コマンドが実行される。
- interact コマンド全体への引数が一行に収まらない場合、"brace"することで各行の終りにバックスラッシュを入れるのを避けることができる。この場合、Tcl の展開はブレースしてあっても起こる。
-
例えば、以下のコマンドは続く string body の組と対話する。 : ^Z が押されると
Expect はサスペンドする。 (
-reset フラグは、端末モードを復旧させる。) ^A が押されると、ユーザーには"you typed a control-A"が返る。
$ が押されると、ユーザーには日付が返る。 ^C が押されると、 Expect は、exit する。 "foo"が入力されると、ユーザーに "bar"が返る。 ~~ が押されると、 Expect インタプリタは、対話モードになる。
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"}
~~
}
- string と body の組で、string が引数として並べられた順に比較される。部分的にマッチした文字列は、残りが到着するまで送られて来ない。何文字かさらに打ち込まれて、マッチが可能になると、今のマッチを判断するためにだけ使われて他のマッチを始めることはしない。それゆえ、部分的にマッチしている文字列のマッチが完了するのは遅れることがある。部分的にはマッチするが最終的にはマッチしない文字列の場合などである。
-
デフォルトでは、ワイルドカードを含まないマッチは、exactとなる。 (
expect コマンドがデフォルトでグロブスタイルのパターンを用いるのとは対照的に。)
-ex フラグは、パターンをプロテクトするのに使える。
interact フラグがそうするように。パターンが"-"で始まる場合、この方法で保護できる。 ("-"で始まる文字列は全て将来のオプションとして予約されている。)
interact -input $user_spawn_id timeout 3600 return -output \
$spawn_id
パターン null と nulls は、( remove_nulls コマンドを通して)、アスキーの 0 にマッチした場合にアクションを実行する。 glob や regexp で 0 バイトにマッチさせることはできない。
このパターンの前に -iwrite フラグをつけると、変数 interact_out(spawn_id) にパターン(あるいはeof)にマッチした spawn_id が設定される。
break や continue といったアクションは、制御構造 (すなわち、 for や proc )の中で、通常通りに動く。しかし、 return は、interact を呼出元に復帰させる。一方、 inter_return は、 interact をその呼びだし元内に復帰させる。例えば、"proc foo"は interact を呼ぶ。interact が、さらに、 inter_return を実行すると proc foo が復帰する。(これは、 interact が、 interpreter を呼んで return とタイプすると、そのinteractは継続するが、 inter_return すると、その呼出元に復帰してしまうということである。)
- interact の間 raw モードが使用されるので、全ての文字が現プロセスに渡される。現プロセスがジョブコントロールシグナルを捕まえなければ、ストップシグナル(デフォルト^Z)で停止する。再スタートするには、制御シグナルを送る。("kill -CONT <pid>"とか打って)。本当に SIGSTOP をプロセスに送りたいなら、csh を spawn してその上でプロセスを起動すること。そうでなくて、 Expect そのものに SIGSTOP を送りたいなら、インタプリタを呼び出して (普通はエスケープ文字)、その後 ^Z を打つこと。
- string bodyのペアは、インタプリタに入ってコマンドを対話的に実行するのを避けることを簡単に書くのに使われる。前の端末モードが、その body を実行する間使用される。
- 実行速度を上げるには、デフォルトでアクションが raw モードで動くようにする。 -reset フラグは、端末の持っているモードをリセットする。そうしなければ、その前に行なった interact コマンドの端末モード(cooked モードとか)が保持される。モードが切り替わった時に、それまで打っていた文字が消えてしまうことがあるので注意すること。(システムによっては、そういう不幸な仕様をした端末ドライバが動いている。) -reset を使うのは、アクションが cooked モードでしか動かない場合だけである。
- -echo フラグは、一文字づつパターンにマッチする文字を返す。これは、ユーザーが打つ文字に部分的にマッチしなければならない場合に有効である。
-
パターンはエコーされたがマッチには失敗した場合、文字列は、spawn されたプロセスに送られる。それから、spawn されたプロセスが文字列を表示し、ユーザーは文字列を二度見る。
-echo は、ユーザーがパターンを完成させてくれそうもない場合にだけ有効であろう。例えば、以下は rftp(リカーシブ ftp スクリプト)からの抜粋だが、ユーザーが ~g, ~p, ~l を打つとカレントディレクトリから再帰的(リカーシブ)に get, put, list する。通常の ftp ではこれらの操作ができない。間違って ~ を打つか、~ の後を間違えた場合、その文字列を無視するようになっている。
interact {
-echo ~g {getcurdirectory 1}
-echo ~l {getcurdirectory 0}
-echo ~p {putcurdirectory}
}
-nobuffer フラグは、文字が読まれる度に、その文字をマッチへ送る。このフラグは、パターンをエコーバックする時に有効である。例えば、以下は誰かが(ヘイズモデムを)ダイアルするのを監視するのに使われる。"atd"が見える度にスクリプトが残りのラインをログする。
proc lognumber {} {
interact -nobuffer -re "(.*)\r" return
puts $log "[exec date]: dialed $interact_out(1,string)"
}
interact -nobuffer "atd" lognumber
- interact の間、前に使った log_user は無視される。特に、 interact は、その出力を記録される(標準出力に送られる)。というのは、ユーザーはエコーバックのない状態でキーを打ちたくはないだろうと考えるからである。
- -o フラグは、現プロセスの出力に key body ペアの key を結びつける。こんな場合に便利である。例えば、telnet セッション中に望まない文字を送ってくるホストを扱う場合である。
- デフォルトでは、 interact は、ユーザーが Expect プロセス自身の標準入力に書き込み、標準出力を見ていると思っている。 -u フラグ("user"のu)は interact に、引数で付けられた名前(spawned id である)のプロセスをユーザーとして扱う。
- これにより、変なループなしに2つの無関係なプロセスを結合させることができる。デバッグする時の助けとして、Expect は常に診断結果を stderr へ送る。(ある種のログとデバッグ情報は stdout に送られる)。同じ理由で、 interpreter コマンドは、stdin からデータを読む。
-
例えば、以下の断片はログインプロセスを作る。そして、(表示されない) ユーザーにダイアルし、両方の接続を行なう。もちろん、loginをどんなプロセスに変えても良い。例えば、シェルなら、アカウントとパスワードを与えなくてもユーザーが起動できる。
spawn login
set login $spawn_id
spawn tip modem
# dial back out to user
# connect user to login
interact -u $login
複数のプロセスへの出力を行なうため、 -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 フラグも指定できる。2つの入力プロセスはデフォルトで $spawn_id と $user_spawn_id に出力される。もし、 -input フラグが -output フラグなしで指定された場合、プロセスからの文字は捨てられる。
-i フラグは、現在の spawn_id を書き換える。ただし、 -input または -output フラグが使われていない場合である。-i フラグは-o フラグを含む。
間接 spawn id を使って会話しているプロセスを切替えることが可能である。 (間接 spawn id は、expect コマンドの項で説明した) 間接 spawn id は、-i, -u, -input, -output フラグで指定できる。
- interpreter [args]
- は、ユーザーに Expect と Tcl コマンドのためのプロンプトを表示する。各コマンドの結果が表示される。
- break や continue は制御構造(すなわち、 for proc )で、通常通りに動く。しかし、 return は、呼出元への復帰を行なうのに対し、 inter_return は interpreter を、呼出元を復帰させる。たとえば、 "proc foo"は、 interpreter を呼び、 inter_return を実行し proc foo が復帰する。他のコマンドは interpreter に新しいコマンドのためのプロンプトを表示し続ける。
- デフォルトでは、プロンプトは 2 つの整数を含んでいる。最初の数は評価スタックの深さ(つまり、何回 Tcl_Eval が呼ばれたか) 2番めの数は、Tcl ヒストリ識別番号である。プロンプトは "prompt1"と呼ばれるプロシジャを定義することで設定できる。このプロシジャの帰り値が次のプロンプトとなる。記述に開きクオート、括弧、ブレース、ブラケットがあると、次の行には第 2 プロンプトが現れる(デフォルトは "+> ")。第 2 プロンプトは "prompt2"と呼ばれるプロシジャを定義することで設定できる。
- interpreter の間は、呼出元が raw モードであったとしても、cooked モードが使われる。
- stdin が閉じられると、 interpreter は -eof フラグが使われていない限り復帰する。使われている場合は引き続く引数を実行する。
- log_file [args] [[-a] file]
-
ファイル名が与えられると、
log_file は、(現時点からの)セッションのログをそのファイルに採取する。引数がなければ、
log_file は記録をやめる。使っていたログファイルはクローズされる。
- log_user -info|0|1
-
デフォルトでは、send/expect ダイアログは標準出力にロギングされる。 (開いていればログファイルにもロギングされる。) "log_user 0"とすると、標準出力へのロギングが抑止される。 "log_user 1"とすると、復旧する。ログファイルへの記録については変更はない。
- match_max [-d] [-i spawn_id] [size]
- は、バッファサイズを(バイト単位で)定義する。このバッファは、 expect の内部で使われる。引数 size がないと、現在のサイズを復帰する。
- -d フラグを指示すると、デフォルトサイズが設定される。(初期状態のデフォルト値は 2000。) -i フラグを指示すると、名前つき spawn id に対してサイズが設定される。指定しなければ、カレントプロセスに対して設定される。
- overlay [-# spawn_id] [-# spawn_id] [...] program [args]
- は、 program args を現在の Expect プログラム上で実行する。現在の Expect プログラムは終了する。ただのハイフンが引数に指定されると、コマンド名の前にハイフンをつけてログインシェルとして扱う。全てのクローズ中の spawn_id は、引数に使われた文字列を待つ。
-
Spawn_id は、新しいプログラムに継承させるためのファイル ID にマップされる。例えば、以下の行はチェスを行ない、chess master という現プロセスに制御させる。
overlay -0 $spawn_id -1 $spawn_id -2 $spawn_id chess
これは、 "interact -u"とするよりも効果的である。しかし、 Expect プロセスが制御していないのでプログラム能力が犠牲となる。
- 制御されない端末ができてしまうことに注意すること。それで、 disconnect するか標準入力をリマップするとジョブ制御プログラム (シェル、ログインなど)が正しく機能しない。
- parity [-d] [-i spawn_id] [value]
- は、spawn idの出力からパリティを保持するか取り除くかを設定する。 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
-
string を現プロセスに送る。例えば、以下のコマンド:
send "hello world\r"
は、文字 h e l l o <blank> w o r l d <return> を現在のプロセスに送る。 (Tcl は、printf に似たコマンド ( format と呼ばれる )を持っていて、複雑な文字列を組み立てることができる。)
-
文字は直ちに送られる。ただし、入力にラインバッファのあるプログラムでは、リターンコードが送られるまで文字が読まれない。リターンコードは、 "\r"と表記する。
set send_human {.1 .3 1 .05 2}
send -h "I'm hungry. Let's do lunch."
ハングさせてしまった後は、以下のようにした方が良いだろう。:
set send_human {.4 .4 .2 .5 100}
send -h "Goodd party lash night!"
send にエラーや修正を埋め込んであっても、エラーはシミュレートされない点に注意すること。ブレークを送るためや、ゆっくりした出力を行なったり、人間が出力したように見せかけるためにヌル文字を送るフラグは相互排他される。最後に指定されたものだけが使われる。それ以上は、 string の引数が、ヌル文字あるいはブレークを送るフラグとして指定できる。
最初の send より前に expect を置いた方が良い。 expect は、プロセスが始まるのを待てるが、 send は待てない。特に、最初の send は、プロセスが走り始める前に完了する。あなたのデータが無視される危険がある。最初にプロンプトを表示しないような対話的なプログラムでは、次のように send の前にディレイをつけることができる:
# どのように破るかのヒントをハッカーに与えてしまわないように、
# このシステムでは外部のパスワードに対するプロンプトを提供しない。
# exec が完了するのを 5 秒待て。
spawn telnet very.secure.gov
sleep 5
send password\r
exp_send は send のエイリアスである。あなたが Expectk か、Tk 環境で動く Expect の他の変種を使っている場合、 send は、全く異なった目的のために使われている。 exp_send が、両環境の間での互換性のために提供されている。似たようなエイリアスが他の Expect の他の send コマンドのために提供されている。
- send_error [-flags] string
- send と似たようなもので、現プロセスでなく stderr に出力される。
- send_log [--] string
- send と似たようなもので、ログファイルだけに string を送る。( log_file を参照。) 引数はログファイルが open されていなければ無視される。
- send_tty [-flags] string
- send と似たようなもので、現プロセスでなく /dev/tty へ出力を送る。
- send_user [-flags] string
- send と似たようなもので、現プロセスでなく標準出力へ出力を送る。
- sleep seconds
- は、与えられた数字の秒数だけスクリプトがスリープする。 seconds は、10進数だけが許される。 (Expectk を使っている場合、Tkのイベントと)割り込みは、Expectがスリープしている間も処理される。
- spawn [args] program [args]
- program args を走らせる新しいプロセスを生成する。その標準入力と標準出力は Expect に結びつけられる。それで、他の Expect コマンドで読んだり書いたりできる。接続は close によって、あるいは、プロセスそのものがファイル ID のいずれかをクローズした場合、破壊される。
- プロセスは spawn によって始められる。変数 spawn_id には、そのプロセスへの参照を行なう識別子が設定される。 spawn_id によって記述されるプロセスは current process が考慮される。 spawn_id は、読んでも書いても良く、効果的なジョブ制御を提供する。
-
user_spawn_id はユーザーを参照する識別子の入ったグローバル変数である。例えば、
spawn_id が、この値に設定された場合、
expect は、
expect_user のような動きをする。
-
tty_spawn_id は、/dev/tty を参照する識別子の入ったグローバル変数である。 /dev/tty が存在しない(cron, at, バッチスクリプトの中)場合、
tty_spawn_id は定義されない。以下のように確認することができる。:
if {[info vars tty_spawn_id]} {
# /dev/tty exists
} else {
# /dev/tty doesn't exist
# probably in cron, batch, or at script
}
- spawn UNIX プロセス ID を復帰する。spawn されたプロセスがない場合、0 が復帰する。変数 spawn_out(slave,name) は pty スレーブデバイスの名前に設定される。
- デフォルトでは、 spawn はコマンド名と引数をエコーする。 -noecho フラグで spawn がこうするのを止められる。
-
-console フラグは、コンソールへの出力を起こし、spawn されたプロセスへのリダイレクトされる。この機能は未サポートのシステムがある。
-
普通、
spawn は、実行するのにわずかの時間しかかからない。spawn に時間をかけたいのであれば、おそらく割り込まれた pty に遭遇するだろう。たくさんのテストが間違ったプロセスに掛かり合うことを避けることができる。 (割り込まれた pty につき、10 秒かかる。)
-d オプションをつけて Expect を走らせると、
Expect がおかしな状態のたくさんの pty に遭遇しているかどうかが表示される。これらの pty がつながっているためにプロセスを殺せない場合、リブートするしか頼れる復旧手段はない。
- strace level
-
は、以降の命令を実行する前に表示を行なう。 (Tcl の trace は、変数のトレースを行なう。)
level は、トレースへの呼び出しスタックの深さを示す。例えば、以下のコマンドは
Expect 最初の 4 レベルの呼び出しをトレースする。それ以上の深さはトレースしない。
expect -c "strace 4" script.exp
-info フラグを指定すると、strace は最後の info でない指定の内容を復帰する。
- stty args
-
は端末モードを変更する。外部の stty コマンドと似たようなものである。
-
以下の例は、一時的なエコー禁止をどうやっておこなうかを示す。これは、他の自動スクリプトで、その中にパスワードが埋め込まれるのを防ぐことに使われる。(もっと議論したければ、下の EXPECT ヒントにある。)
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 ロカールだけがサポートされる。-seconds フラグは、タイムスタンプを秒で表す。
-gmt GMT タイムゾーンで出力する。デフォルトはローカルタイムゾーンである。
- trap [[command] signals]
-
を実行すると、以降指定された signal を受けとると指定された
command を実行する。このコマンドは、グローバルスコープで実行される。もし、
command が指定されなければ、シグナルアクションが復帰する。
command が、文字列 SIG_IGN であれば、シグナルが無視される。
command が、文字列 SIG_DFL であれば、シグナルはデフォルトの動きをする。
signals は、シグナルが1つでも複数のシグナルのリストでも良い。シグナルは、数字とsignal(3)に記述されている文字列のどちらで指定しても良い。プレフィクスの"SIG"は、省略しても良い。
trap exit {SIGINT SIGTERM}
-Dフラグを使ってデバッガを起動するなら、SIGINT が再定義されてから対話型デバッガが起動される。これは以下の trap によって起こる。
trap {exp_debug 1} SIGINT
デバッガのトラップは、環境変数 EXPECT_DEBUG_INIT を設定して、新しく trap を起動することで変更できる。もちろん、スクリプトにtrapコマンドを足すだけで例のトラップは両方とも上書きできる。特に、自作の "trap exit SIGINT"があるなら、これはデバッガのトラップを上書きしてしまう。ユーザーにデバッガを全く触らせないようにするのに、便利である。
SIGINT のトラップを独自に定義したいけれど、デバッガにも同時に割り込んでもらいたいのであれば、こう書く。:
if ![exp_debug] {trap mystuff SIGINT}
代わりに、別のシグナルを使ってデバッガに割り込みをかけることができる。trap は、SIGALRM のアクションを上書きしない。 Expect が内部で使用しない。 disconnect コマンドは、SIGALRMを SIG_IGN (ignore)に設定する。後から発行される spawn コマンドを実行している間、SIGALRM はディスエーブル中であれば再度イネーブルにできる。
もっと情報が欲しい場合、signal(3) を参照すること。
- wait [args]
- は、spawn されたプロセス(あるいは、名前つきのプロセスがなければ現在のプロセス) が終了するのを待つ。
-
wait は、通常 4 つの整数のリストを帰す。最初の整数は、終了を待ち構えているプロセスの pid である。 2 つめの整数は、関連する spawn id である。 3 つめの整数は、オペレーティングシステムエラーがあれば -1、そうでなければ、0 である。 3 つめの整数が 0 であれば、4 つめの整数はspawnされたプロセスからのリターンコードである。3 つめの整数が -1 であれば、4 つめの整数はオペレーティングシステムによって設定された errno の値である。グローバル変数 errorCode も設定される。
-
-i フラグによって、wait を行なう名前付き spawn_id(プロセス ID ではなく)を指定する。SIGCHLD ハンドラの内部では、spawn ID -1 を指定することで、 spawn されたプロセスのいずれかを wait できる。
ライブラリ
Expect は、スクリプトのための二つのビルトインライブラリを自動的に理解する。それらは変数 exp_library と変数 exp_exec_library に設定されたディレクトリ名として定義される。これらのディレクトリには、他のスクリプトによって使えるユーティリティファイルが入っている。整形印刷
Expect スクリプトをきれいに印刷するための vgrind の定義がある。 Expect ディストリビューションと一緒に配布されている vgrind 定義が正しくインストールされていると仮定して、こうすれば使える。
vgrind -lexpect file
例
マニュアルページによる記述では、あらゆるものをどう組み合わせるのかということが明白ではない。私としては、 Expect ディストリビューションの example ディレクトリにある例を読んで試してみて欲しいと思う。いくつかは本物のプログラムである。それ以外は特定のテクニックの単純な解説、あと、もちろん、単なるクイックハックが少しある。 INSTALL ファイルにはこれらのプログラムの簡単な梗概が書かれている。Expect の論文も役に立つ(関連項目参照)。いくつかは、初期バージョンの Expect の文法を使っているが、それとともにある根本的な考えは、なお有効であり、このマニュアルページより詳細に書かれている。
警告
拡張は Expect のコマンド名と衝突するかもしれない。例えば、 send は、Tk では全く別の目的で定義されている。そういう理由で、ほとんどの Expect コマンドは、"exp_XXXX"という別の記法(エイリアス)もサポートする。 "exp", "inter", "spawn", "timeout"で始まるコマンドと変数は、エイリアスを持たない。この環境間の互換性が必要であれば、拡張されたコマンド名を用いること。バグ
プログラムに "sex" ("Smart EXec"か "Send-EXpect"の略) という名前をつけるのは実に魅力的だったのだが、センスの良い方(あるいは、単にピューリタニズム)が優先された。
set env(TERM) vt100
tipt (BSDI BSD/OS 3.1 i386 でのみ確認)は、SHELL と HOME が設定されてないとハングする。この問題は cron や at や CGI スクリプトで使う時に問題だ (設定しないので)。それで、明示的に指定しなくてはならない - 本当のタイプは通常関係ない。何かに設定してないと駄目なのだ! 以下の記述はおそらくほとんどの場合を満足させるだろう。
set env(SHELL) /bin/sh
set env(HOME) /usr/local/bin
pty のインプリメントの中には、プロセスがファイル記述子をクローズした後、読んでない出力を 10 から 15 秒後(この値はインプリメントに依存)に投げてくるものがある。それゆえ、
spawn date
sleep 20
expect
のような Expect プログラムは失敗する。失敗しないように非対話的なプログラムでは spawn せずに exec すること。こういう状況は考えられるが、実際には私はまだその状態に陥ったことがない。つまり、この原因で対話プログラムの最後の出力を取りこぼすという状態になったことがない。
一方、Cray UNICOS の pty は読み込んでいない出力をファイルディスクリプタをプロセスが閉じるとすぐに投げてくる。私は Cray にこの動きについて報告し、回避するための修正を行なった。
プロンプトと応答の間にディレイが必要な場合がある。tty インターフェースが UART の設定を変えたり、スタート/ストップビットを探してボーレートを合わせている時などである。通常、ここで必要とされているものは 1 秒か 2 秒の待ち合わせである。もっとしっかりしたやり方はハードウェアが入力を受ける準備ができるまで繰り返すことである。以下の例は、両方の戦略を採用している。:
send "speed 9600\r";
sleep 1
expect {
timeout {send "\r"; exp_continue}
$prompt
}
-code をトラップするのは Tcl のイベントループに依存しているコマンド (sleep など)では動かない。問題は、イベントループの中では Tcl は非同期イベントハンドラからの返り値を捨てているからである。回避方法としては、トラップコードでフラグをセットし、コマンド(sleep など)の直後でフラグをチェックすることである。
EXPECT ヒント
Expect について、直観的でない点が少しある。このセクションではそういったことの指摘とそれに対する示唆を試みる。
set prompt "(%|#|\\$) $" ;# default prompt
catch {set prompt $env(EXPECT_PROMPT)}
expect -re $prompt
私としては、見えると思っているものの終りの部分を含んだ expect パターンを書くように勧める。そうすれば、全体を見る前に応答を返してしまうことを避けることができる。さらに、全体が見える前に答えることもできるがその文字は質問に混ざって echo される。言い替えれば、会話は正常だが見た目は混ざって見える。
ほとんどのプロンプトの終りの文字はスペースである。例えば、 ftpからのプロンプトは、'f', 't', 'p', '>' そして <blank>である。このプロンプトにマッチさせるには、この文字の一つ一つにマッチしなければならない。blank を含めないのは、よくある誤りである。明示的に blank を入れるように。
X* の形のパターンを使うのであれば、* はXから最後に受けとる何かまでの全てにマッチする。これは一見直観的だが、"最後に受けとる何か"がコンピュータの速度とカーネルとデバイスドライバによるI/O処理によって変わってしまうので混乱するだろう。
特に、人間はプログラムの出力が巨大なひとまとまりとしてやってくると思いがちだが、実際には、ほとんどのプログラムは一行を一度に送る。こう仮定すると、直前の段落のパターンにあった * は、行の終りにしかマッチしないかも知れない。たとえ、もっと入力があると思われる場合でも出力を全て受けとった時点でしかマッチを行なわないのである。
expect は、指定したパターンが教えてくれない限り、出力がさらにやってくるのかどうかわからない。
行指向のバッファリングを行なっている場合ですら、賢いやり方とはいえない。プログラムはバッファリングのタイプをめったに保証しないだけでなく、その際に消化不良を起こして出力行を中断し、行末をバッファ中のランダムな位置に置いてしまう。それで、パターンを書く時には、プロンプトの最後の何文字かを入れておいた方が賢いやり方といえるのである。
プログラムの最後の出力にあるパターンを待ち、実際には何がしか他のものが発行されている場合には timeout キーワードで判定することができない。理由は、 expect が、タイムアウトしない-そのかわりに eof を見つけられる-からである。代わりになるものを使うこと。もっと良いのは、両方使うことだろう。行が回り込んだ場合でも、行そのものを編集するべきではない。
tty ドライバによって出力される時に newline は、普通 carriage return, linefeed の組に変換される。それで、2 ラインにマッチするには、例えば、printf("foo\nbar") にマッチさせるには、"foo\r\nbar"というパターンを使うことになる。
似たような変換は、ユーザーから expect_user を通して入力を読み込む時にも起こる。この場合、ユーザーがリターンを押すと、それは newline に変換される。その後、 Expect が端末モードを raw モード(telnet のような)を設定すると、問題が発生する可能性がある。プログラムが本当のリターンを待ってしまうからである。(プログラムの中には、newline をリターンに変えても大丈夫なものもあるが、ほとんどはだめである。) 残念ながら、プログラムが端末を raw モードにすることを検出する方法がない。
手で newline をリターンに変えるのではなく、"stty raw"を使うのが解決策である。 stty raw は、変換を停止する。しかし、こうすると cooked モードの行編集機能が使えなくなるということに注意すること。
interact は、端末を raw モードに設定するので、この問題は発生しない。
Expect スクリプトの中でパスワード(や他の機密情報)を保存すると便利なことはよくある。だれかのアクセスを受けるとその影響を受けてしまうコンピュータでそういうことをするのは勧められない。それで、パスワードのプロンプトをスクリプトから出すのが、文字通りパスワードを埋め込むよりは、良い考えといえる。とはいうものの、そういった埋め込みをするしかない場合もある。
不幸なことに、UNIX のファイルシステムは「実行可能で読めない」スクリプトを作る直接の方法がない。setgid シェルスクリプトをサポートしたシステムでは、次のようにすることで間接的にシミュレートできる。
, Expect スクリプト(機密情報の入った)を普通に作る。そのパーミッションを 750 (-rwxr-x---) に設定し、trusted group つまり、読んでも良いグループの所有とする。必要なら、この目的のための新しいグループをつくること。次に、/bin/sh スクリプトをパーミッション
2751 (-rwxr-s--x) で、同じグループの所有で作成する。
こうすると、シェルスクリプトはだれからも実行でき(かつ、読め)る。実行すると、それは Expect スクリプトを実行する。
関連項目
Tcl(3), libexpect(3)著者
Don Libes, National Institute of Standards and Technology謝辞
Tclを生み出した John Ousterhout と、インスピレーションを与えてくれた Scott Paisley に感謝する。 Expect のオートコンフィギュレーションコードについて、 Rob Savoye に感謝する。HISTORY ファイルに expect の進化の大部分が記述されている。このファイルは面白く読めて、かつ、あなたのこのソフトウェアへの洞察をより深くするだろう。このファイルに書かれている、私にバグフィックスを送ってくれた人たちや、他の援助をしてくれた人たちに感謝する。
Expect の設計と実装は、部分的にアメリカ政府からその対価をもらっているので、パブリックドメインである。しかし、このプログラムとドキュメントあるいはその一部が使われたなら、著者と NIST への謝辞を述べてもらいたい。
29 December 1994 |