Linux expectパッケージを使ったSSHサーバへの接続および"set timeout"の罠
Linuxのexpectパッケージを使ってSSHサーバへ自動接続するシェルを作成したのですが
気づきがあったのでシェアしたいと思います。
(入力を自動化するexpectパッケージ自体についての導入法等の解説はここでは割愛しますので興味のある方は検索してみてください。)
expectを使ったシェルスクリプトのサンプル①
#!/bin/bash
#予めSSHサーバの接続情報を変数に格納
user=sshuser
pass=sshpass
target=xxx.xxx.xxx.xxx
expect -c "
set timeout 10
spawn sshpass -p ${pass} ssh -o StrictHostKeyChecking=no -l ${user} ${target}
expect "$ "
send "date >> /tmp/date.log"
expect "$ "
send "exit"
"
上記がexpectのよくある使用例となります。(接続先サーバからプロンプトが返ってきたらdateの実行結果をファイルに書き込むだけ。)
少し補足するとsend文が増えると考慮する対話が増えてしまうのでここではSSHコマンドと
接続先/認証/ホスト鍵の確認をワンライナーで記載して
シンプルになるようにしています。
(接続先パスワードもシェル内にべた書きしていますのであくまで検証用の例です。)
"set timeout 10"の挙動について
記事タイトルにインパクトを付けるため大げさに記載しましたが本題に入ります。結論から書くと
「set timeoutはexitではない」ということになります。
どういうことかと言うと例えば上記サンプル①のコード場合だと接続先のSSHサーバが何らかの理由により
接続できない状態になっていたとします。
一見すると「"set timeout 10"を指定してるから10秒経ってもサーバに繋がらなければシェルは終了するだろう。」
と思われるかもしれません。
(実際にそのように読み取れるような解説をしているサイトもありました。)
しかし10秒経過しサーバに繋がらなくても実際には次の処理(send~~)は実行されるため、
本来リモートで実行したかったコマンドがローカルで実行される可能性があります。
では実際にサーバに接続できなかった場合には次の処理に行かないようにするには
どのようにすれば良いでしょうか。
スマートな解決策が分からなかったので泥臭くいくことにします。
expectを使ったシェルスクリプトのサンプル②
#!/bin/bash
#予めSSHサーバの接続情報を変数に格納
user=sshuser
pass=sshpass
target=xxx.xxx.xxx.xxx
expect -c "
set timeout 10
spawn sshpass -p ${pass} ssh -o StrictHostKeyChecking=no -l ${user} ${target}
expect {
"$ " { }
timeout { exit 2 }
}
send "date >> /tmp/date.log"
expect "$ "
send "exit"
"
expectは条件分岐を利用することができるためサーバに繋がらない(timeout)時はその時点でexitすることにしましょう。また上記サンプル②では終了コード"2"を指定しているので
SSH接続できなかった場合は終了コードからエラーログを出力するようにすることもできます。
解説は以上となります。
(初めて技術ブログの記事を執筆しましたがなぜ得意でもないシェルネタになってしまったのかは自分でも分かりません。)