標準入力から応答していくタイプのプログラムがよくあります。
hello.sh
#!/bin/sh echo "what's your name?" echo "input your name:" read name echo "age ?:" read age echo "hi, $name($age)! nice to meet you!!"
$ sh hello.sh
what's your name?
input your name:
ryozi <= 入力
age ?:
24 <= 入力
hi, ryozi(24)! nice to meet you!!
これを自動化したい!と思ったら、迷わずexpectを使うわけですが、
expectがなく、シェルスクリプトしかない環境で同等なことをやれ!と言われて、
何か間違ってると思いながら、無い頭をひねって考えた結果が以下。(シェルはbash)
こんな感じに、ある入力に対してある出力をするようなコードを書きます。
expect.sh
#!/bin/sh while read INPUT; do echo "INPUT:$INPUT" 1>&2 # デバッグ出力のため case $INPUT in *"input your name"*) echo "ryozi" ;; *"age ?"*) echo "24" ;; esac done # tailf時の終了のための空出力 echo
$ touch temp
$ chmod 600 temp
$ tailf temp | sh hello.sh | sh expect.sh > temp
INPUT:what's your name?
INPUT:input your name:
INPUT:age ?:
INPUT:hi, ryozi(24)! nice to meet you!!
$ rm temp
標準入力は目に見えないので変な感じですが、いい感じに動いています。
一時ファイルを使うので後始末を忘れずに。
また、名前付きパイプを知ったので、これをどうにか使ってみたのがこちら。
一時ファイルにデータが残らないので衛生的かもしれない。tailfではなく、catを使ってます。
$ mkfifo --mode=600 temp
$ cat temp | sh hello.sh | sh expect.sh > temp
INPUT:what's your name?
INPUT:input your name:
INPUT:age ?:
INPUT:hi, ryozi(24)! nice to meet you!!
$ rm temp
CentOSなら大体標準で入ってるだろう機能だけでできそうですね...
欠点
1行入力は改行文字も含んでいるので、このままcaseでマッチさせるときは*を後ろにつけて前方一致するようにしたりする必要があったり。
上記の例では、入力要求時に一々改行を入れてますが、
見栄え重視な対話プログラムの場合、実際の入力は以下のようになるでしょう。
what's your name?
input your name:ryozi <= 入力
age ?:24 <= 入力
hi, ryozi(24)! nice to meet you!!
こんな感じに、標準入力を求める前の出力で改行が無い場合が多いです。
改行文字が出力されないと、パイプで渡せないっぽいので注意が必要です。(readの問題かもしれないけど)
こういうケースでは、上記のexpect.shでは応答できません。その前の1行で判断するなり対応が必要です。
expectコマンドはこの問題は気にせずに、かつ、きれいに書けますね。
expect << EOF set timeout 10 spawn sh hello.sh expect "input your name:" send "ryozi\n" expect "age ?:" send "24\n" expect eof EOF
spawn sh hello.sh
what's your name?
input your name:ryozi
age ?:24
hi, ryozi(24)! nice to meet you!!
出力もきれい。なんで使っちゃだめなんや...
9/19 追記
readで指定文字づつ読む -n オプションをつけることで、対応できるかもしれません。
readコマンドはシェルに組み込まれてるコマンドらしいので、もしかしたらうまくいかないかもしれません。
#!/bin/sh # 空白を読み飛ばさないようにするため、IFSをsetしなおす IFS="" # バッファ準備 MESSAGE= # 期待するメッセージに対するレスポンスを定義(後方一致がおすすめ) # 基本的に、期待するメッセージを処理できたら0、処理できない場合は1を返す。 expect_message(){ MESSAGE=$1 # ここでメッセージ処理 case $MESSAGE in *"input your name") echo "ryozi" return 0 ;; *"age ?") echo "24" return 0 ;; esac return 1 } # 読み込み while read -n 1 CHARACTER; do # 読めなかったら、たぶん改行か何かなので、バッファをクリアする if [ "$CHARACTER" = "" ]; then MESSAGE= continue fi MESSAGE="$MESSAGE$CHARACTER" # 処理できたらバッファをクリア expect_message $MESSAGE && MESSAGE= done # tailf用 echo
上記のexpect.shで対応できなかったものも、対応できるようになります。