Linuxでcoreutils が利用可能なら、timeout コマンドを使いましょう。なお、Mac であれば brew install coreutils
して gtimeout
が使えます。
timeout
Usage: timeout [OPTION] NUMBER[SUFFIX] COMMAND [ARG]...
or: timeout [OPTION]
man には以下のように書かれています。デフォルトの挙動が使いやすそうです。
コマンドがタイムアウトし、--preserve-status が指定されていない場合、 終了ステータスは 124 になります。それ以外の場合、COMMAND の終了ステータスが 終了ステータスになります。シグナルが指定されていない場合、タイムアウト時には TERM シグナルが送られます。この TERM シグナルにより、TERM シグナルをブロック もしくは捕捉していないプロセスは、すべて終了されます。 KILL (9) シグナルを 使う必要がある場合もあります (KILL シグナルは捕捉することができません)。 KILL (9) シングルが送信された場合は、終了ステータスは 124 ではなく 128+9 になります。
watch とか nice とかと並んで使い勝手が良さそう。
たとえばスローテストを弾くとか
時間のかかるテストを実行して、タイムアウトしたら問答無用で kill してメール送りたいなどの用途にも活躍します。
timeout -sKILL 3600 rake spec
STATUS=$?
if [ "$STATUS" -eq 124 ];
then
echo "ちょっとテスト長すぎるんじゃない?"
exit 124
fi
(これはかなり乱暴なコードなので、安定して運用するなら別途子プロセスの始末もなども考慮してください)
たとえば一定時間内のログの行数を数えたい時とか
以下のようにして、10秒毎にアクセスログの数をカウントしたり。
$ while true; do : | timeout 10 tail -n 0 -f /var/log/nginx/access.log | wc -l; done
1321
1532
801
39
4
0
0
0
たとえば非同期処理の実行を監視するとか
一定時間ログを眺めて、特定の文字列が出てくるかどうか監視したりもできます。
timeout 10 tail -n 0 -f /var/log/httpd/error_log | grep -q "shutdown"
PST=("${PIPESTATUS[@]}")
GREP_STATUS=${PST[1]}
TIMEOUT_STATUS=${PST[0]}
if [ "$GREP_STATUS" = "0" ] ;
then
echo "やったね、シャットダウンされたみたいだよ"
exit 0
fi
if [ "$TIMEOUT_STATUS" = "124" ] ;
then
echo "時間内にシャットダウンされなかったよ。おかしいよ"
exit 124
fi
echo "あれれ、なにかおかしいよ・・・?"
自作の関数をタイムアウトさせるとか
便利ですね!
ということで
シェルスクリプトを眺めていて、ときおり一定時間経過後に kill
するための複雑な並行処理を見かけたりしますが、timeout
が利用できる環境では積極的に利用していきたいところです。
また、バッチ処理などで気軽に叩いてる ls -1
とかもNFS周りで簡単に刺さったりするので、timeout
が有益なケースって割といろいろありそう。
もちろん、こいつはシグナル送るだけなので複雑な条件判定がしたい時には個別に作るべきだと思います。