expectで対話式のコマンドを自動化する

LinuxやMacなどで、コマンドを実行する際にパスワードを求められることが多々あると思います。
例えば、、、sudoコマンドの実行やsshでのリモートログインなど。

このようなコマンドをシェルスクリプトとして実行したい場合はどのようにすれば良いのでしょうか。
シェルスクリプトで自動的にパスワードを入力するには、どのようにすれば良いのでしょうか。

今回は、このようなマシンとの対話形式のコマンドをexpectを用いて自動化する手法について記述していきます。

expectコマンドを用いると、どのようなことができるのか

例えば、私が今さっと思いつく限りでは、、、

  • sudo権限の必要なコマンドでのパスワード入力の自動化
  • sshでリモートログインする際のパスワードの自動入力
  • git push時のパスワード入力

などなど、、、。
expectコマンドを用いると、マシンとの対話が必要なコマンド実行時に必要な入力操作を自動的に行うことができます。
アイデア次第で色々応用が効きそうなコマンドです。

私のUbuntuではネットワークマネージャが頻繁におかしくなるので、
“$ sudo service network-manager restart”
このコマンドを、expectを用いて自動的に実行できるスクリプトを書いています。

expectコマンドをインストールする

expectコマンドはubuntuではapt-getコマンドでインストールすることができます。

$ sudo apt-get install expect

Macの方はbrewでインストールできると思います。

$ brew install expect

expectコマンドの仕組み

expectコマンドを使用する際に覚えておくコマンドは以下の通りです。

set timeoutコマンドのタイムアウトの秒数を設定します。(デフォルトでは10[s])
-1を指定した場合にはタイムアウトは行われません。
spawnコマンドを送ります。
expectマシンからの応答を読み取り、パターンマッチングを行います。
sendマシンの応答に対しての返答を行います。

expectコマンドでの対話処理の流れとしては、以下のようになります。

  1. マシンにコマンドを送る(spawnコマンドによる処理)
  2. マシンからの返答
  3. 返答の文字列に対してパターンマッチング(expectコマンドによる処理)
  4. マッチングの結果に応じてコマンドを送信(sendコマンドによる処理)

sudoコマンドでのパスワード入力を自動化してみる

使い方として、次のサンプルコードを見ながら説明してみようと思います。
上の方でも少し触れましたが、Ubuntuのネットワークマネージャを再起動するコマンド、
“$ sudo service network-manager restart”のパスワード入力を自動化して見ました。

#!/bin/sh
timeout=10
password="your_password"
command="sudo service network-manager restart"
expect -c "
    set timeout ${timeout}
    spawn ${command}
    expect \"sudo\"
    send \"${password}\n\"
    expect \"$\"
    exit 0
"
exit 0

サンプルコードを解説していきます。

timeout=10
タイムアウトの時間を設定します。
この時間を超えてもマシンからの返答がなかった場合には、実行を中止します。
-1に設定した場合にはタイムアウトは行いません

password=” ~ “
ここにパスワードを格納しておく変数です。
後に、このpassword変数をsendコマンドにてマシンに入力しています。

command=” ~ “
実行したいコマンドを入力します。
(例えば、”sudo apt-get updateなど)

expect -c ” ~ “
実際にexpectで対話コマンドを実行する部分になります。
” ~ “(ダブルクォーテーション)で囲まれた中で、マシンとの対話を実行します。

set timeout ${timeout}
タイムアウトの時間をセットします。
先ほどの変数でtimeout=10に設定しているので、タイムアウトは10秒となります。
何かコマンドを実行して、10秒間マシンからの返答がなかった場合処理が中断されます。

expect \”sudo\”
この行で、マシンからの返答に対するパターンマッチングを行います。
sudoコマンドを実行すると、”[ sudo ] xxxxxのパスワード : “と言うようにパスワードの入力が求められます。
この文字列をパターンマッチングにかけ、sudoの文字列が見つかれば次の処理に移行します。

send \”${password}\n\”
次に、sendコマンドでパスワードの入力を行います。
パスワードの文字列を送ります。
入力の後には改行コード ‘\n’ も忘れずに入力しましょう。
みなさんも、端末に入力した後はエンターキーを押しますよね。\nはその代わりです。
\nが無いと、マシンはいつまでたっても入力を待ち続けます。

expect \”$\”
最後にもう一度expectで$(ドルマーク)の出現を待ちます。
この処理は無くでも良いのですが、パスワードの入力処理が終わり次の行の$マークが出現するのを待ってから
expect処理を終了した方が気持ちが良いので、私はこの方法を用いています。

exit 0
最後の行まで到達できたら(途中でタイムアウトしなければ)、exit 0(正常終了)でexpectコマンドを終了します。

expectコマンドを用いると色々なコマンドを自動化でき、処理が捗りますね!
以上、expectで対話式のコマンドを自動化する方法についてでした。