対話型のコマンドを自動化できるexpectコマンド | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

expectコマンドは、対話形式のコマンドを自動で実行したいといった場合に便利です。

例えば、SSHでサーバーに接続する際、パスワード認証が用いられている場合は当然パスワードを入力しないとログインできません。

または、何らかのソフトウェアをコマンドラインからインストールする場合に、その過程で幾つか環境の状態により質問されたり(パスを求められたり、ディレクトリ名を指定するなど)します。


こういったコマンドを自動化したい場合は、予めその実行コマンドにスキップするためのオプションなどが用意されていないと対応が難しかったりもしますが、expectコマンドを利用すればそれらのほとんどを回避することが可能になります。



SSHで接続してリモートサーバーのディスク容量を確認する


例えばサーバー運用者があるサーバーにログインしてディスク容量を定期的に確認しているとします。(あまり目視監視するってことも無いとは思いますが・・・)

サーバーにログインして、また別のサーバーにログインし・・・、となるとかなり手間にもなったりしますのでexpectコマンドでそれらの流れをまとめ、自動化するためのスクリプトを作ってみます。


#!/bin/bash

expect -c "
set timeout 10
spawn ssh -l work srv01
expect \"Are you sure you want to continue connecting (yes/no)?\" {
    send \"yes\n\"
    expect \"work@srv01's password:\"
    send \"password\n\"
} \"work@srv01's password:\" {
    send \"password\n\"
}
expect \"\[work@srv01 ~\]$\"
send \"df -k\n\"
send \"exit\n\"
interact
"


流れを簡単に説明すると、最初のexpect -cオプション以降でこれから実行するコマンドを指定します。

実際にコマンドを実行する箇所はspawnを記述した箇所(SSH接続)になります。


spawn ssh -l work srv01

上記で、workユーザーでサーバーsrv01へ接続しています。

次のexpectは、コマンドを実行した際に標準出力に表示されるメッセージを指定しています。


expect \"Are you sure you want to continue connecting (yes/no)?\" {

最初にSSH接続した場合は、リモートサーバーの公開鍵を保存するか質問されるので、もしこのメッセージが表示されたら、sendオプションで「yes」を送信するように指定しています。

公開鍵の取り込みが終わったらパスワードが求められるので


expect \"work@srv01's password:\"

と、workユーザーのパスワード求めるプロンプトが表示されたら


send \"password\n\"

sendオプションでworkユーザーのパスワードを自動で渡すように指定しています。

このようにexpectコマンドは、基本的にexpectで期待するメッセージ、そしてsendオプションでそれに対しての入力内容をセットしていきます。

また、2度目以降のSSH接続の場合は、いきなりパスワードを求められるのでその場合のパターンも用意しています。


} \"work@srv01's password:\" {
    send \"password\n\"
}

これは、プログラムのIF文と同様で条件分岐させて、幾つかの質問されるパターンを予め用意しておくことで、spawnで実行したコマンドの自動処理に柔軟性を持たせることが可能になります。


[work@localhost workspace]$ ./expect.sh
spawn ssh -l work srv01
work@srv01's password:
Last login: Tue Oct 19 23:50:09 2010 from localhost.localdomain
df -k
exit
[work@srv01 ~]$ df -k
Filesystem           1K-ブロック    使用   使用可 使用% マウント位置
/dev/vzfs             10485760   1036888   9448872  10% /
none                   4150972         4   4150968   1% /dev
[work@srv01 ~]$ exit
logout

サーバー切断までの処理を自動化できるため、リモートサーバーに接続してコマンド入力などの一連の流れを一つのシェルスクリプト実行だけで済ませることができます。



SCPを使ったファイル転送の自動処理


定期的に特定のファイルやディレクトリをリモートサーバーに転送(または取得)したいという場合の一連のコマンドを自動化してみます。


#!/bin/bash

USER=work
PASS=password
HOST=srv01

FROM_DIR=/home/work/workspace/messages.log
TO_DIR=/home/backup/

expect -c "
set timeout 10
spawn scp $USER@$HOST:$FROM_DIR $TO_DIR
expect \"Are you sure you want to continue connecting (yes/no)?\" {
    send \"yes\n\"
    expect \"$USER@$HOST's password:\"
    send \"$PASS\n\"
} \"$USER@$HOST's password:\" {
    send \"$PASS\n\"
}
interact
"


リモートサーバー(srv01)上にあるmessages.logというファイルをローカルサーバーにダウンロードします。

シェルスクリプトと組み合わせて実行することもできるため、今回はユーザー名やパスワード、転送先などの情報を変数に格納して実行しています。

逆のパターン(リモートサーバーへファイルを転送したい)は、scpのオプションを入れ替えるだけで対応できるでしょう。


$ ./scp-get.sh
spawn scp work@srv01:/home/work/workspace/messages.log /home/backup/
work@srv01's password:
messages.log                                     100%   26KB  25.5KB/s   00:00

もちろんこのシェルスクリプトは、パスワードを含むためにセキュリティ上あまりよろしくありません。

SSHやSCPをパスワードなしで転送したい場合は、リモートホストとホストベース認証や公開鍵認証方式を設定しておきパスワードなしでログインできるようにしておいたほうが良いかもしれません。

(そもそもホストベース認証ができるのであれば、わざわざexpectコマンドを利用する必要はありませんけど・・・)

設定方法は、関連記事内のエントリを参考にしてみてください。


いつもやっているあの作業だけど、幾つか環境の状態によって入力する情報が違うんだけど・・・って場合なんかに便利に使えますね。

運用・保守のいつもの作業もかなり効率化できるのではないでしょうか。