簡単なゲームを作成して自動化することで Expect を学びます
Expect で「数字を推測する」ゲームをコーディングします。次に、ゲームのプレイを自動化する別のスクリプトを使用して、Expect の真の力を学びます。
ワークフローを自動化しようとしているときに、有意義な自動化を妨げる構成ユーティリティを思いつきました。これは、サイレント インストーラーや stdin
をサポートしていない Java プロセスであり、一貫性のないプロンプト セットがありました。 Ansible の expect
モジュールは、このタスクには不十分でした。しかし、expect
コマンドはそのための単なるツールであることがわかりました。
Expect を学ぶ私の旅は、Tcl を少し学ぶことを意味しました。 簡単なプログラムを作成するための基礎知識が得られたので、Expect でのプログラミングをよりよく学ぶことができます。この由緒あるユーティリティの優れた機能を示す記事を書くのは楽しいだろうと思いました。
この記事は、典型的な単純なゲーム形式を超えています。 Expect の一部を使用してゲーム自体を作成する予定です。次に、ゲームのプレイを自動化する別のスクリプトを使用して、Expect の真の力を示します。
このプログラミング演習では、変数、入力、出力、条件評価、ループなどの古典的なプログラミングの例をいくつか示します。
期待をインストールする
Linux ベースのシステムの場合は、以下を使用します。
$ sudo dnf install expect
$ which expect
/bin/expect
私のバージョンの Expect が macOS の基本オペレーティング システムに含まれていることがわかりました。
$ which expect
/usr/bin/expect
macOS では、brew を使用して少し新しいバージョンをロードすることもできます。
$ brew install expect
$ which expect
/usr/local/bin/expect
Expectの数字を推測してください
Expect を使用した数字推測ゲームは、前の記事で使用した基本的な Tcl とそれほど変わりません。
Tcl 内のすべてのものは、変数値を含む文字列です。コード行は (行継続を使用するのではなく) 中括弧で囲むのが最適です。角括弧はコマンドの置換に使用されます。コマンド置換は、他の関数から値を導出する場合に便利です。必要に応じて入力として直接使用できます。これらすべては後続のスクリプトで確認できます。
新しいゲーム ファイル numgame.exp
を作成し、実行可能に設定して、以下のスクリプトを入力します。
#!/usr/bin/expect
proc used_time {start} {
return [expr [clock seconds] - $start]
}
set num [expr round(rand()*100)]
set starttime [clock seconds]
set guess -1
set count 0
send "Guess a number between 1 and 100\n"
while { $guess != $num } {
incr count
send "==> "
expect {
-re "^(\[0-9]+)\n" {
send "Read in: $expect_out(1,string)\n"
set guess $expect_out(1,string)
}
-re "^(.*)\n" {
send "Invalid entry: $expect_out(1,string) "
}
}
if { $guess < $num } {
send "Too small, try again\n"
} elseif { $guess > $num } {
send "Too large, try again\n"
} else {
send "That's right!\n"
}
}
set used [used_time $starttime]
send "You guessed value $num after $count tries and $used elapsed seconds\n"
proc
を使用すると、関数 (またはプロシージャ) 定義が設定されます。これは、関数の名前、その後にパラメータを含むリスト (1 つのパラメータ {start}
)、その後に関数の本体が続く構成で構成されます。 return ステートメントは、ネストされた Tcl コマンド置換の良い例を示しています。 set
ステートメントは変数を定義します。最初の 2 つはコマンド置換を使用して、乱数と現在のシステム時刻 (秒単位) を保存します。
while
ループと if-elseif-else ロジックはよく知られているはずです。行の継続を必要とせずに複数のコマンド文字列をグループ化するのに役立つ中括弧の特定の配置にもう一度注目してください。
ここで (以前の Tcl プログラムとの) 大きな違いは、puts
と ではなく関数
を取得します。 expect
と send
を使用していることです。 >expect
と send
を使用すると、Expect プログラム自動化の中核が形成されます。この場合、これらの関数を使用して、端末での人間の作業を自動化します。後で実際のプログラムを自動化できます。このコンテキストで send
コマンドを使用することは、単に情報を画面に出力するだけです。 expect
コマンドはもう少し複雑です。
expect
コマンドは、処理ニーズの複雑さに応じて、いくつかの異なる形式を取ることができます。一般的な使用法は、次のような 1 つ以上のパターンとアクションのペアで構成されます。
expect "pattern1" {action1} "pattern2" {action2}
より複雑なニーズでは、複数のパターン アクションのペアを中括弧内に配置し、オプションで処理ロジックを変更するオプションをプレフィックスとして付けることができます。上記で使用したフォームは、複数のパターンとアクションのペアをカプセル化しています。オプション -re
を使用して、(glob 処理ではなく) 正規表現処理をパターンに適用します。この後に、実行する 1 つ以上のステートメントを中括弧で囲みます。上記で 2 つのパターンを定義しました。 1 つ目は、1 つ以上の数字の文字列と一致することを目的としています。
"^(\[0-9]+)\n"
2 番目のパターンは、数字の文字列以外のものと一致するように設計されています。
"^(.*)\n"
この expect
の使用は、while
ステートメント内から繰り返し実行されることに注意してください。これは、複数のエントリを読み取るための完全に有効なアプローチです。自動化では、反復処理を行う Expect のわずかなバリエーションを示します。
最後に、$expect_out
変数は、expect
が処理の結果を保持するために使用する配列です。この場合、変数 $expect_out(1,string)
には、正規表現の最初にキャプチャされたパターンが保持されます。
ゲームを実行する
ここで驚くべきことはありません。
$ ./numgame.exp
Guess a number between 1 and 100
==> Too small, try again
==> 100
Read in: 100
Too large, try again
==> 50
Read in: 50
Too small, try again
==> 75
Read in: 75
Too small, try again
==> 85
Read in: 85
Too large, try again
==> 80
Read in: 80
Too small, try again
==> 82
Read in: 82
That's right!
You guessed value 82 after 8 tries and 43 elapsed seconds
あなたが気づくかもしれない 1 つの違いは、このバージョンが示す焦り です。長く躊躇していると、無効なエントリによるタイムアウトが発生することが予想されます。その後、再度プロンプトが表示されます。これは、無期限に待機する gets
とは異なります。 expect
タイムアウトは構成可能な機能です。これは、ハングしたプログラムや予期しない出力時の対処に役立ちます。
Expect でゲームを自動化する
この例では、Expect 自動化スクリプトは numgame.exp
スクリプトと同じフォルダーにある必要があります。 automate.exp
ファイルを作成して実行可能にし、エディタを開いて次のように入力します。
#!/usr/bin/expect
spawn ./numgame.exp
set guess [expr round(rand()*100)]
set min 0
set max 100
puts "I'm starting to guess using the number $guess"
expect {
-re "==> " {
send "$guess\n"
expect {
"Too small" {
set min $guess
set guess [expr ($max+$min)/2]
}
"Too large" {
set max $guess
set guess [expr ($max+$min)/2]
}
-re "value (\[0-9]+) after (\[0-9]+) tries and (\[0-9]+)" {
set tries $expect_out(2,string)
set secs $expect_out(3,string)
}
}
exp_continue
}
"elapsed seconds"
}
puts "I finished your game in about $secs seconds using $tries tries"
spawn
関数は、自動化するプログラムを実行します。コマンドを個別の文字列として受け取り、その後に渡す引数を受け取ります。推測する最初の数字を設定すると、本当のお楽しみが始まります。 expect
ステートメントはかなり複雑で、このユーティリティの威力を示しています。ここには、プロンプトを反復するためのループ ステートメントがないことに注意してください。私のゲームには予測可能なプロンプトがあるため、expect
にもう少し処理を依頼できます。外側の expect
は、 `==>` のゲーム入力プロンプトと一致しようとします。それを見て、send
を使用して推測し、さらに追加の expect
を使用して推測の結果を把握します。出力に応じて、変数が調整および計算されて、次の推測が設定されます。プロンプト `==>` が一致すると、exp_ continue
ステートメントが呼び出されます。これにより、外側の expect
が再評価されます。したがって、ここでのループは不要になります。
この入力処理は、Expect の処理の別の動作に依存します。 Expect は、パターンに一致するまで端末出力をバッファリングします。このバッファリングには、埋め込まれた行末やその他の印刷不可能な文字が含まれます。これは、Awk や Perl で慣れている一般的な正規表現行のマッチングとは異なります。パターンが一致すると、一致後のものはすべてバッファーに残ります。次の試合の試行で使用できるようになります。これを利用して、外側の expect
ステートメントをきれいに終了させました。
-re "value (\[0-9]+) after (\[0-9]+) tries and (\[0-9]+)"
内部パターンが正しい推測と一致し、ゲームによって出力されるすべての文字を消費していないことがわかります。文字列の最後の部分 (経過秒数) は、推測が成功した後もバッファリングされます。外側の expect
の次の評価では、この文字列がバッファから照合され、きれいに終了します (アクションは提供されません)。さて、楽しい部分として、完全な自動化を実行してみましょう。
$ ./automate.exp
spawn ./numgame.exp
I'm starting to guess with the number 99
Guess a number between 1 and 100
==> 99
Read in: 99
Too large, try again
==> 49
Read in: 49
Too small, try again
==> 74
Read in: 74
Too large, try again
==> 61
Read in: 61
Too small, try again
==> 67
Read in: 67
That's right!
You guessed value 67 after 5 tries and 0 elapsed seconds
I finished your game in about 0 seconds using 5 tries
おお!自動化によりマイナンバー推測効率が飛躍的に向上!いくつかの試行実行では、平均して 5 ~ 8 の推測が得られました。また、常に 1 秒以内に完了します。この厄介で時間のかかる楽しみがすぐに実行できるようになったので、家の改善プロジェクトに取り組むなど、他のより重要なタスクを遅らせる言い訳はできません :P
決して学習をやめないでください
この記事は少し長かったですが、取り組む価値は十分にありました。数字推測ゲームは、Expect 処理のより興味深い例をデモンストレーションするための優れた基盤を提供しました。この演習からかなりのことを学び、作業の自動化を正常に完了することができました。このプログラミング例が興味深いものであり、自動化の目標をさらに進めるのに役立つことを願っています。