ウェブサイト検索

簡単なゲームを作成して自動化することで 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 ではなく関数 expectsend を使用していることです。 >を取得します。 expectsend を使用すると、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 処理のより興味深い例をデモンストレーションするための優れた基盤を提供しました。この演習からかなりのことを学び、作業の自動化を正常に完了することができました。このプログラミング例が興味深いものであり、自動化の目標をさらに進めるのに役立つことを願っています。

関連記事: