timeout コマンド入門 - 実行時間を制限してハングを防ぐ

timeout コマンド入門 - 実行時間を制限してハングを防ぐ

この記事でできるようになること

  • コマンドの実行時間に上限を付けて、固まったまま放置される事故を防げる
  • 終了ステータス `124` を見てタイムアウトかどうかを判定できる
  • `-k` で SIGTERM を無視するプロセスを確実に止められる

前提知識(先に読むと理解しやすい記事)

この記事で学べること

  • timeoutコマンドの実行時間に制限 をかけられるようになる
  • 「固まって戻ってこない」コマンドを 自動で打ち切る 方法が分かる
  • タイムアウトしたかどうかを 終了ステータス 124 で判定できる
  • SIGTERM を無視するしぶといプロセスを -k で強制終了 できる

結論(先に覚えるべき型)

  • 時間を区切って実行 → timeout 10 コマンド(10 秒で打ち切り)
  • 単位を付けてもよい → timeout 30s / timeout 5m / timeout 1h
  • 打ち切られたかの判定 → 終了ステータスが 124 ならタイムアウト
  • それでも止まらないとき → timeout -k 5 10 コマンド(5 秒後に強制終了)

先に覚える「固まったように見えるとき」の抜け方

コマンドが戻ってこないと、画面が壊れたように感じる。しかし、たいていは次の順で抜けられる。

見えている状態 抜け方
コマンドが終わらずプロンプトコマンドの入力を待っている状態を示す記号(例 $ や #)。が出ない Ctrl+C を押して中止する
Ctrl+C でも止まらない Ctrl+Z で一時停止し、jobs で番号を見て kill %1
毎回この状態になるのを避けたい 最初から timeout 10 コマンド のように上限を付けて実行する

timeout は「そもそも固まったまま放置しない」ための予防策。だから毎回 Ctrl+C を押す必要がなくなる。

1. timeout コマンドとは何か?

結論: timeout は「指定した時間を超えたらコマンドを自動で終了させる」コマンド。ハング対策の定番。

リナ: 先輩、ダウンロードのコマンドを実行しました。でも、いつまでも終わらなくて画面が固まっちゃったんです...。Ctrl+C で止めるしかないんですか?
ライニー先輩: そういうとき便利なのが timeout だよ。「このコマンドは最大 N 秒だけ動かす。それを超えたら自動で止める」 という時間制限をかけられるんだ。
リナ: 自分で Ctrl+C を押さなくても、勝手に止めてくれるんですか?
ライニー先輩: そう。だから「固まったまま放置」を防げる。とくに cron(クーロン。決めた時刻に自動でコマンドを動かすしくみ。「定期実行」「スケジューラ」とも呼ぶ)で動かす処理では効果が大きい。
ライニー先輩: 固まったコマンドが残り続けると、あとの処理まで止まってしまうからね。
リナ: なるほど、timeout はその保険なんですね。

ハング(hang)とは

ハングは「コマンドが終わらないまま止まって見える状態」のこと。「フリーズ」「固まる」「応答がない」も、ほぼ同じ意味で使われる。プログラムがこわれているとはかぎらない。相手の返事をずっと待っているだけのこともある。

timeout の基本イメージ

timeout 10 コマンド
        ↑   ↑
        |   実行したいコマンド
        制限時間(秒)

「10 秒以内に終われば普通に終了、超えたら強制的に打ち切り」。

2. なぜ timeout が必要なのか?

結論: ネットワーク待ちや無限ループで固まるコマンドを放置すると、スクリプト全体が止まる。timeout がそれを防ぐ。

リナ: でも、ちゃんと動くコマンドなら時間制限はいらないですよね?
ライニー先輩: 普段はね。問題は 「いつ終わるか分からない」コマンド なんだ。応答しないサーバーへの接続、消えた相手への ping、書き方を間違えた無限ループ。こういうのは 永遠に戻ってこない ことがある。
リナ: たしかに、さっきのダウンロードがまさにそれでした...。
ライニー先輩: 人間が見ていれば Ctrl+C で止められる。でも cron や自動化スクリプトには 見ている人がいない
ライニー先輩: 固まったプロセスが居座ると、次の処理も始まらない。timeout で上限を決めておけば、最悪でも「N 秒で諦めて次へ進む」ようにできるんだ。

固まったコマンドを放置すると、メモリやプロセス枠を食いつぶす。cron の多重起動を招くこともある。「いつ終わるか分からない処理には時間の上限を付ける」 のが安全な運用。

3. 基本の使い方(時間の指定)

結論: timeout 時間 コマンド の形。数字だけなら秒、s/m/h/d で単位も付けられる。

いちばん基本の形は次の通り。

$ timeout 10 sleep 30

sleep 30 は本来 30 秒待つコマンド。だが timeout 10 を付けると 10 秒で打ち切られる

リナ: sleep 30 なのに 10 秒で終わった! 約束の 30 秒より早く止まりましたね。
ライニー先輩: そう、それが時間制限の効果。数字だけ書くと という意味になる。単位を付けて分かりやすく書くこともできるよ。

時間には単位(サフィックス)を付けられる。

$ timeout 30s コマンド    # 30秒
$ timeout 5m コマンド     # 5分
$ timeout 1h コマンド     # 1時間
$ timeout 2d コマンド     # 2日

時間の単位(サフィックス)

書き方 意味
10 10秒(単位なし=秒)
30s 30秒
5m 5分
1h 1時間
2d 2日

0.5 のような小数も使える(timeout 0.5 コマンド で0.5秒)。

4. 終了ステータス 124 でタイムアウトを判定する

結論: timeout で打ち切られたときの終了ステータスは 124echo $?124 ならタイムアウトと判断できる。

終了ステータス(exit status)とは

終了ステータスは、コマンドが終わるときに残す番号のこと。「終了コード」「リターンコード」「exit code」も同じものを指す呼び方だよ。0 は成功。0 以外は「何か普通ではないことが起きた」という意味になる。直前のコマンドの値は $? で見られる。

timeout が時間切れでコマンドを止めたとき、終了ステータスは 124 になる。これを見れば、タイムアウトしたかどうかを自動で見分けられる。

$ timeout 1 sleep 10
$ echo $?
124
リナ: 124 という数字が出ました。これがタイムアウトの目印なんですね。
ライニー先輩: その通り。逆に、コマンドが時間内にちゃんと終わった場合は そのコマンド自身の終了ステータス が返る。成功なら 0 だね。
ライニー先輩: だから 124 かどうかを見れば「途中で打ち切られたのか、ちゃんと終わったのか」が分かる。

時間内に終わった場合は、コマンド本来のステータスが返る。

$ timeout 10 sleep 1
$ echo $?
0

覚えておきたい終了ステータス

意味
124 タイムアウトで打ち切られた
125 timeout コマンド自体の失敗
126 コマンドは見つかったが実行できない
127 コマンドが見つからない
137 KILL シグナル(-k)で強制終了された(128 + 9)

まずは 「124 = タイムアウト」 だけ覚えれば十分。

まれに、コマンド自身が 124 を返すこともある。そこまで区別したいときは --preserve-status を使う。

5. -k でしぶといプロセスを強制終了する

結論: timeout はデフォルトで SIGTERM を送る。無視されるときは -k 時間 で猶予後に SIGKILL を送って確実に止める。

プロセス(process)とは

プロセスは「今このコンピュータの中で動いている、プログラム 1 つ 1 つ」のこと。あなたが打ったコマンドも、動き始めるとプロセスになる。

シグナル(signal)とは

シグナルは、プロセスに送る短い合図のこと。「終わってください」「今すぐ止まれ」といった内容を番号と名前で送る。よく使うのは 3 つだけ。

  • SIGTERM: 終了のお願い。プログラム側はあとしまつをしてから終われる
  • SIGINT: Ctrl+C を押したときに送られる合図
  • SIGKILL: 強制終了。プログラム側は拒否できない
リナ: timeout で止めたはずなのに、たまにコマンドが止まらないと聞きました。どうしてですか?
ライニー先輩: いい質問。timeout は時間が来ると、まず SIGTERM(終了のお願い) を送る。でも、このお願いを 無視するプログラム もあるんだ。
リナ: 「止まってね」とお願いしても聞いてくれないんですね...。じゃあどうすれば?
ライニー先輩: そこで -k(kill-after)を使う。「最初のお願いから N 秒待っても止まらないなら、今度は強制終了する」という二段構えにできるんだ。
$ timeout -k 5 10 コマンド

これは次の意味になる。

  • まず 10 秒 でコマンドに SIGTERM(終了のお願い)を送る
  • それでも さらに 5 秒 止まらなければ SIGKILL(強制終了)を送る

-k(kill-after)の動き

timeout -k 5 10 コマンド
         ↑  ↑
         |  本来の制限時間(10秒)→ SIGTERM
         追加の猶予(5秒)→ それでも残れば SIGKILL

SIGKILL はプログラム側が拒否できない。どうしても止めたいときの、いちばん最後の手だて。

SIGKILL(強制終了)は あとしまつをする間を与えずに プロセスを止める。書きかけのファイルが壊れる可能性もある。まずは普通の timeout(SIGTERM)で試そう。止まらないときの保険として -k を使うのがよい。

6. シグナルを指定する(-s)

結論: -s で送るシグナルを変えられる。終了時にコマンド自身のステータスを返したいなら --preserve-status

デフォルトでは SIGTERM が送られる。-s--signal)を使えば、別のシグナルを指定できる。

$ timeout -s SIGINT 10 コマンド

これは Ctrl+C と同じ SIGINT を送る。コマンドによっては、SIGTERM より行儀よく終われることがある。

リナ: シグナルって、さっきの SIGTERM とか SIGKILL のことですよね。種類があるんですか?
ライニー先輩: たくさんあるよ。timeout でよく使うのは 3 つ。お願い系の SIGTERM(デフォルト)と SIGINTCtrl+C 相当)、いちばん最後の手だてになる SIGKILL だね。
ライニー先輩: シグナルそのものは少し奥が深い。別記事(trap 入門)でじっくり扱うよ。

タイムアウトしたときに 124 へ書き換えず、コマンドが最期に返した値をそのまま受け取りたい ときは --preserve-status を使う。

$ timeout --preserve-status 10 コマンド

--preserve-status を付けると、SIGTERM で止められた場合は 143(128 + 15)が返る。「コマンドが正常に終わったときの値」が返るわけではない。「シグナルで止められた」という事実まで含めて知りたい場面で使う。通常は付けない。124 で判定する方がシンプル。

7. よくある初心者のつまずき

結論: 時間とコマンドの順番、124 の意味、-k の引数の並びを取り違えやすい。

7-1. 時間とコマンドの順番を逆にする

# NG: コマンドが先になっている
$ timeout sleep 30 10

# OK: 時間 → コマンドの順
$ timeout 10 sleep 30

timeout のすぐあとは かならず時間。その後に実行したいコマンドを書く。順番を逆にすると timeout がエラーになる。

7-2. 124 を「エラー」と勘違いする

リナ: 先輩、大変です!さっきのスクリプトが 124 を返しました。私、コマンドを壊しちゃったんでしょうか...?
ライニー先輩: 落ち着いて。124「timeout が時間どおり仕事をした」証拠 なんだ。
リナ: えっ、失敗の合図じゃないんですか?
ライニー先輩: 違うよ。コマンドの不具合ではない。「時間内に終わらなかったので、予定どおり打ち切った」という意味なんだ。
リナ: なんだ、そういうことか...!じゃあ 124 を見たら「上限に達した」と読めばいいんですね。安心しました。

7-3. パイプ全体にかけたつもりで一部しか制限されない

# これは「timeout 10 で grep」だけにかかる書き方ではない
$ timeout 10 grep pattern file | sort

パイプ(|。左のコマンドの結果を、右のコマンドへ渡す書き方)でつないでも、制限がかかるのは timeout 10 grep pattern file の部分だけ。| sort は別扱いになる。

パイプ全体を一括で囲みたいときは、bash -c 'コマンド列'(まとめてひとつのコマンドとして扱わせる書き方)を使う。

$ timeout 10 bash -c 'grep pattern file | sort'

8. ミニ課題:実際にやってみよう

結論: 打ち切り・終了ステータス確認・強制終了の 3 問で timeout を手で確かめる。

リナ: 知識は入りました! 手を動かしてみたいです。
ライニー先輩: いいね、3 問用意したよ。題材は sleep にしてある。消えて困る中身が無いので、失敗しても何も壊れない。

課題 1: sleep 30 を 3 秒で打ち切る

やること: 本来 30 秒かかるコマンドを、3 秒で終わらせよう。

ヒント1(方向づけ)を見る

制限時間を先に書き、そのあとに実行したいコマンドを書く。数字だけを書いた場合の単位は何になるか思い出そう。

ヒント2(コマンド名)を見る

使うのは timeout。形は timeout 時間 コマンド。数字だけなら秒の意味になる。

答えを見る
$ timeout 3 sleep 30

3 秒でプロンプトが戻る。sleep の 30 秒を待たされない。

課題 2: タイムアウトしたか確かめる

やること: 課題 1 のすぐあとに終了ステータスを表示して、124 になることを確かめよう。

ヒント1(方向づけ)を見る

直前のコマンドの結果は、特別な変数に入っている。それを表示するコマンドを使おう。

ヒント2(コマンド名)を見る

直前の結果は $? に入っている。表示は echo $?

答えを見る
$ timeout 3 sleep 30
$ echo $?

124 が表示されればタイムアウト成功。

課題 3: 止まらないときに強制終了する

やること: sleep 30 を「5 秒で SIGTERM、さらに 2 秒待っても止まらなければ SIGKILL」で止める指定を書こう。

ヒント1(方向づけ)を見る

二段構えにするオプションがある。猶予の秒数と、本来の制限時間の 2 つを並べて書く。

ヒント2(コマンド名)を見る

使うのは -k。並び順は -k 猶予秒 制限秒 コマンド

答えを見る
$ timeout -k 2 5 sleep 30

sleep は SIGTERM で素直に止まる。だから 5 秒で終わり、SIGKILL までは進まない。

リナ: できました!「時間が先、コマンドが後」で、結果は 124 を見る。この 2 つが軸ですね。
ライニー先輩: そのとおり。あとは止まらない相手に -k を足すだけ。この 3 つで、固まったまま放置される事故はほぼ防げるよ。

9. コピペ用テンプレート

結論: 基本・単位付き・判定・強制終了・パイプのよく使う型をまとめて手元に置いておく。

よく使う型をまとめておく

# 基本:10秒で打ち切る
timeout 10 コマンド

# 単位を付ける(秒 / 分 / 時)
timeout 30s コマンド
timeout 5m コマンド

# タイムアウト判定(124 ならタイムアウト)
timeout 10 コマンド
echo $?

# 止まらないときは強制終了(10秒 + 猶予5秒で SIGKILL)
timeout -k 5 10 コマンド

# パイプ全体に制限をかける
timeout 10 bash -c 'コマンドA | コマンドB'

# Ctrl+C 相当(SIGINT)で止める
timeout -s SIGINT 10 コマンド

今日の 3 行まとめ

  • timeout 時間 コマンド で実行時間に上限を付けられる。順番は 時間が先、コマンドが後
  • 終了ステータス 124 はタイムアウトの合図。エラーではなく「予定どおり打ち切った」という意味
  • SIGTERM を無視するプロセスには -k 猶予秒 制限秒 で二段構えにする。SIGKILL はいちばん最後の手だて

次に読む

この記事を共有

次の一手