Linux
tips
coreutils
シェルスクリプト

一定時間でシェルコマンドを終了させるたった1つの方法

More than 1 year has passed since last update.

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 が有益なケースって割といろいろありそう。

もちろん、こいつはシグナル送るだけなので複雑な条件判定がしたい時には個別に作るべきだと思います。