- Linux
Linux timeout・sleepコマンドの使い方 時間制限と待機の書き方
timeout は「コマンドを制限時間つきで実行する」コマンド、sleep は「指定した時間だけ待つ」コマンド、wait は「バックグラウンドで動かした処理の完了を待つ」シェルの組み込みコマンドです。 3つは別々に紹介されることが多いのですが、スクリプトでは「一定時間で打ち切る」「少し待ってからやり直す」「並列に動かして全部終わるのを待つ」と組み合わせて使う場面がほとんどです。
この記事では3つを1本にまとめ、目的別の早見表、終了ステータス 124・125・126・127・137 の一覧、-k・--preserve-status・--foreground の有無で結果がどう変わるかを、すべて実際に実行した結果で示します。$! と wait、wait -n での並列待ち、シェル関数やパイプに timeout を掛ける方法、リトライループ、macOS での gtimeout まで扱います。
実行環境は Ubuntu 24.04 LTS、GNU coreutils 9.4(timeout・sleep)、GNU bash 5.2.21 です。オプションの説明と終了ステータスの一覧は、GNU coreutils マニュアルの timeout のページを今回開いて確認しました。
timeout・sleep・wait の早見表
目的別の書き方
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| N秒で打ち切る | timeout 10 コマンド |
時間切れの終了ステータスは 124 |
| 打ち切りに応じないときは強制終了 | timeout -k 5 10 コマンド |
10秒で TERM、さらに5秒後に KILL |
| 時間切れかどうかを判定する | timeout 10 コマンド; rc=$? |
rc が 124 なら時間切れ |
| コマンド自身の終了ステータスを残す | timeout --preserve-status 10 コマンド |
124 に置き換えない |
| 端末から入力を読むコマンドに掛ける | timeout --foreground 10 コマンド |
スクリプトの中から使うとき |
| シェル関数・パイプ全体に掛ける | timeout 10 bash -c '処理' |
関数は export -f で渡す |
| N秒待つ | sleep 5 |
小数(sleep 0.5)も使える |
| N分・N時間待つ | sleep 3m、sleep 1h |
接尾辞は s・m・h・d |
| バックグラウンド処理の完了を待つ | コマンド & pid=$!; wait "$pid" |
wait の終了ステータスがその処理のもの |
| 全部の完了を待つ | wait |
引数なしの終了ステータスは常に 0 |
| どれか1つの完了を待つ | wait -n |
bash 4.3 以降 |
| 条件が満たされるまで待つ(上限つき) | timeout 30 bash -c 'until 条件; do sleep 1; done' |
起動待ちの定型 |
| うまくいくまで繰り返す | for ループ + timeout + sleep | 本文のリトライ関数を参照 |
timeout の終了ステータス
| 終了ステータス | 意味 | 再現したコマンド |
|---|---|---|
| 0 など | コマンドが時間内に終わった(コマンド自身の終了ステータス) | timeout 2 sleep 1 → 0、timeout 2 false → 1 |
| 124 | 時間切れで打ち切った(--preserve-status なし) |
timeout 2 sleep 5 |
| 125 | timeout 自身がエラーになった(時間の書き方など) | timeout abc sleep 1 |
| 126 | コマンドは見つかったが実行できない | timeout 2 ./noexec.sh(実行権限なし) |
| 127 | コマンドが見つからない | timeout 2 nosuchcmd |
| 137 | KILL シグナルで終了した(128+9) | timeout -s KILL 1 sleep 5、-k で KILL まで進んだとき |
| 143 など | --preserve-status を付けたときの、シグナルで終了したコマンドの値(128+15) |
timeout --preserve-status 1 sleep 5 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linux timeout・sleepコマンドの使い方 時間制限と待機の書き方」
timeout で N 秒で打ち切る
基本の形と終了ステータス
timeout 2 sleep 5; echo "rc=$?" # 2秒で打ち切り
timeout 2 sleep 1; echo "rc=$?" # 時間内に終了
timeout 2 false; echo "rc=$?" # 時間内に終了(コマンド自身が 1 を返す)
rc=124
rc=0
rc=1
時間内に終わればコマンド自身の終了ステータス、時間切れなら 124 が返ります。時間切れのとき、timeout は何もメッセージを出しません。画面に知らせたいときは -v(--verbose)を付けます。
timeout -v 1 sleep 5; echo "rc=$?"
timeout: sending signal TERM to command 'sleep'
rc=124
時間の書き方
| 書き方 | 意味 |
|---|---|
timeout 10 コマンド |
10秒(接尾辞なしは秒) |
timeout 0.5 コマンド |
0.5秒(小数も使える) |
timeout 5m コマンド |
5分 |
timeout 2h コマンド |
2時間 |
timeout 1d コマンド |
1日 |
timeout 0 コマンド |
時間制限なし |
timeout 0.5 sleep 5; echo "rc=$?"
timeout 0 sleep 1; echo "rc=$?"
rc=124
rc=0
0 を指定すると時間制限が無効になります。変数で秒数を渡すスクリプトでは、変数が 0 のときに「すぐ打ち切り」ではなく「制限なし」になる点を覚えておくと役立ちます。
124 以外のエラーになる例
timeout 2 nosuchcmd; echo "rc=$?"
touch noexec.sh
timeout 2 ./noexec.sh; echo "rc=$?"
timeout abc sleep 1; echo "rc=$?"
timeout: failed to run command 'nosuchcmd': No such file or directory
rc=127
timeout: failed to run command './noexec.sh': Permission denied
rc=126
timeout: invalid time interval 'abc'
Try 'timeout --help' for more information.
rc=125
「時間切れ」と「そもそも起動できなかった」を終了ステータスで区別できるのが timeout の便利なところです。スクリプトでは次のように分けて扱います。
timeout 10 ./job.sh
rc=$?
case $rc in
0) echo "完了" ;;
124) echo "時間切れ" ;;
125) echo "timeout の指定を見直す" ;;
126) echo "実行権限を確認する" ;;
127) echo "コマンド名・パスを確認する" ;;
137) echo "KILL で強制終了された" ;;
*) echo "コマンドがエラーを返した: $rc" ;;
esac
短く書くなら || と if が使えます。
timeout 1 sleep 5 || echo "代わりの処理を実行"
if timeout 1 sleep 5; then
echo ok
else
rc=$?
[ $rc -eq 124 ] && echo "時間切れ" || echo "別のエラー rc=$rc"
fi
代わりの処理を実行
時間切れ
-k・--preserve-status・-s で結果がどう変わるか
timeout は時間が来ると、既定では TERM シグナル(終了の依頼) を送ります。TERM を受け取ったプログラムは後片付けをしてから終了できますが、TERM に応じない作りのプログラムは動き続けます。この違いを2つのスクリプトで確かめます。
#!/usr/bin/env bash
# stubborn.sh : TERM を受け取っても続行する
trap 'echo "TERM を受け取りましたが続行します"' TERM
for i in 1 2 3 4 5 6; do sleep 1; echo "tick $i"; done
#!/usr/bin/env bash
# graceful.sh : TERM を受け取ったら後片付けをして終了ステータス 3 で終わる
trap 'echo "後片付けをして終了します"; exit 3' TERM
for i in 1 2 3 4 5 6; do sleep 1 & wait $!; echo "tick $i"; done
-k なし:TERM に応じない処理は最後まで動く
time timeout 2 ./stubborn.sh; echo "rc=$?"
tick 1
Terminated
TERM を受け取りましたが続行します
tick 2
tick 3
tick 4
tick 5
tick 6
real 0m6.008s
rc=124
2秒の制限を掛けたのに、処理は6秒かかって最後まで動きました。終了ステータスは 124 なので「時間切れ」とは報告されますが、打ち切られてはいません。途中の Terminated は、スクリプトの中で動いていた sleep が TERM で終了したことを bash が知らせている行です。
-k あり:指定した秒数のあとに KILL を送る
time timeout -v -k 1 2 ./stubborn.sh; echo "rc=$?"
tick 1
timeout: sending signal TERM to command './stubborn.sh'
Terminated
TERM を受け取りましたが続行します
tick 2
timeout: sending signal KILL to command './stubborn.sh'
Killed timeout -v -k 1 2 ./stubborn.sh
real 0m3.004s
rc=137
-k 1 を付けると、TERM の1秒後に KILL シグナルが送られ、合計3秒で終了しました。KILL はプログラム側で受け止められないため、確実に止まります。終了ステータスは 137(128+9)です。Killed の行は、呼び出し元の bash が表示したものです(行頭のスクリプト名と PID は省いています)。
止まらない可能性がある処理には -k を付けるのが定石です。後片付けの時間を見込んで、-k の秒数は数秒〜数十秒にしておくとよいでしょう。
--preserve-status:コマンド自身の終了ステータスを残す
timeout 2 ./graceful.sh; echo "rc=$?"
timeout --preserve-status 2 ./graceful.sh; echo "rc=$?"
tick 1
後片付けをして終了します
rc=124
tick 1
後片付けをして終了します
rc=3
graceful.sh は TERM を受け取ると exit 3 で終わります。--preserve-status なしでは 124、ありではコマンド自身の 3 が返りました。trap を持たないコマンドに付けると、シグナルで終了したことを表す値になります。
timeout --preserve-status 1 sleep 5; echo "rc=$?"
kill -l 143
rc=143
TERM
143 は 128+15(TERM)です。kill -l 数値 でシグナル名を確かめられます。
4つの組み合わせの結果一覧
| コマンド | 対象 | かかった時間 | 終了ステータス |
|---|---|---|---|
timeout 2 |
TERM に応じない処理 | 約6秒(最後まで動いた) | 124 |
timeout -k 1 2 |
TERM に応じない処理 | 約3秒 | 137 |
timeout --preserve-status -k 1 2 |
TERM に応じない処理 | 約3秒 | 137 |
timeout 2 |
TERM で後片付けする処理 | 約2秒 | 124 |
timeout --preserve-status 2 |
TERM で後片付けする処理 | 約2秒 | 3(コマンド自身の値) |
timeout --preserve-status 1 |
sleep(trap なし) | 約1秒 | 143(128+15) |
timeout -s KILL 1 |
sleep | 約1秒 | 137 |
timeout -s INT 1 |
sleep | 約1秒 | 124 |
-s(--signal)は最初に送るシグナルを変えるオプションです。-s INT は Ctrl+C と同じ INT を送ります。-s KILL は最初から強制終了するので、後片付けの機会がなくなります。まずは既定の TERM と -k の組み合わせを選び、-s KILL は後片付けが不要な処理に限るのがおすすめです。
timeout が終了するまでの流れ
flowchart TD
A[timeout 秒 コマンド を実行] --> B{時間内に終わったか}
B -- はい --> C[コマンド自身の終了ステータスを返す]
B -- いいえ --> D[TERM を送る(-s で変更可)]
D --> E{コマンドが終了したか}
E -- はい --> F[124 を返す<br>--preserve-status ならコマンド自身の値]
E -- いいえ --> G{-k の指定があるか}
G -- なし --> H[コマンドが終わるまで待つ<br>終わったら 124]
G -- あり --> I[-k の秒数後に KILL を送る<br>137 を返す]
128 を超える終了ステータスの読み方
シグナルで終了したコマンドの終了ステータスは 128+シグナル番号になります。kill -l 数値 で名前を確かめられます。
kill -l 137; kill -l 143; kill -l 130
KILL
TERM
INT
| 終了ステータス | シグナル | よくある場面 |
|---|---|---|
| 130 | INT(2) | Ctrl+C で止めた |
| 137 | KILL(9) | -k や -s KILL で強制終了した |
| 143 | TERM(15) | kill や timeout の既定のシグナルで終了した |
--foreground の有無で変わること
timeout は既定で、実行するコマンドを 別のプロセスグループに入れてから起動します。これにより、時間切れのときにコマンドが起動した子プロセスにもまとめてシグナルを送れます。その代わり、スクリプトの中から呼ばれたコマンドは端末(TTY)から入力を読めなくなります。--foreground はこの分離をやめるオプションです。
端末から入力を読むコマンド
次の2つのスクリプトを、端末に taro と入力する形で実行しました。
# ask.sh
timeout 3 bash -c 'read -r -p "name: " x < /dev/tty; echo "got=[$x]"'; echo "rc=$?"
# askfg.sh
timeout --foreground 3 bash -c 'read -r -p "name: " x < /dev/tty; echo "got=[$x]"'; echo "rc=$?"
--- ask.sh(--foreground なし)
name: taro
rc=124
--- askfg.sh(--foreground あり)
name: taro
got=[taro]
rc=0
--foreground なしでは、入力しても読み取られず 3秒後に 124 で終わりました。--foreground を付けると入力が届いて got=[taro] が表示されています。スクリプトの中で、確認の入力を求めるコマンドやパスワードを尋ねるコマンドに時間制限を掛けたいときに使います。
子プロセスへのシグナルの届き方
コマンドがさらに子プロセスを起動する場合の違いです。
#!/usr/bin/env bash
# kids.sh : 子プロセスとして sleep 30 を起動して待つ
sleep 30 &
echo "child sleep pid=$!" > kid.pid
wait
| 実行したコマンド | 終了ステータス | 子の sleep 30 |
|---|---|---|
timeout 1 ./kids.sh |
124 | 一緒に終了した |
timeout --foreground 1 ./kids.sh |
124 | 残って動き続けた |
--foreground を付けると、シグナルは直接起動したコマンドだけに届きます。 子プロセスを起動する処理に --foreground を使うときは、コマンド側で子プロセスを終了させる trap を書くなどの配慮が必要です。
| 状況 | 選ぶもの |
|---|---|
| ふつうのスクリプトから、入力を読まないコマンドを実行 | --foreground なし(既定) |
| スクリプトから、端末の入力を読むコマンドを実行 | --foreground あり |
| 子プロセスもまとめて止めたい | --foreground なし(既定) |
sleep で N 秒待つ
書き方と所要時間
time sleep 0.3
time sleep 1 0.5 # 複数指定は合計
time sleep 0.01m # 0.01分 = 0.6秒
real 0m0.305s
real 0m1.501s
real 0m0.602s
| 書き方 | 待つ時間 |
|---|---|
sleep 5 |
5秒 |
sleep 0.5 |
0.5秒 |
sleep 2m |
2分 |
sleep 1h 30m |
1時間30分(複数指定は合計) |
sleep 1d |
1日 |
sleep infinity |
止められるまで待ち続ける |
GNU の sleep は 小数と接尾辞(s・m・h・d)、複数指定に対応しています。sleep infinity は、コンテナやサービスを「何もせず動かし続ける」ときによく使われます。
エラーになる例
sleep
sleep abc
sleep 1s 2x
sleep: missing operand
Try 'sleep --help' for more information.
sleep: invalid time interval 'abc'
Try 'sleep --help' for more information.
sleep: invalid time interval '2x'
Try 'sleep --help' for more information.
いずれも終了ステータスは 1 です。変数で秒数を渡すときは、変数が空だと missing operand になるので、sleep "${WAIT:-5}" のように既定値を付けておくと安心です。
sleep を途中で止める
sleep infinity & p=$!
sleep 0.5
kill $p
wait $p; echo "rc=$?"
rc=143
バックグラウンドの sleep は kill で止められます。終了ステータスは 143(128+15)です。
read -t で待つ方法との比較
bash の組み込みコマンド read にも、-t 秒 で待ち時間の上限を付ける機能があります。入力がないまま時間が来ると、128 を超える終了ステータスが返ります。
time read -t 0.5 _ </dev/zero; echo "read rc=$?"
real 0m0.500s
read rc=142
| 方法 | 向いている場面 |
|---|---|
sleep 秒 |
決まった時間だけ待つ |
read -t 秒 変数 |
利用者の入力を、上限つきで待つ |
timeout 秒 コマンド |
外部コマンドの実行時間に上限を付ける |
wait でバックグラウンド処理の完了を待つ
$! で PID を控えて wait する
コマンドの末尾に & を付けるとバックグラウンドで動きます。直前に起動したバックグラウンド処理の PID は $! に入っているので、すぐ変数に控えます。
(sleep 0.5; exit 7) & pid=$!
wait $pid; echo "rc=$?"
rc=7
wait PID の終了ステータスは、その処理の終了ステータスです。バックグラウンドで動かした処理の成否を、あとから受け取れます。
引数なしの wait は常に 0 を返す
(sleep 0.3; exit 2) & (sleep 0.6; exit 0) &
wait; echo "wait(all) rc=$?"
wait(all) rc=0
1つ目の処理は 2 で終わっていますが、引数なしの wait は全部を待ったうえで 0 を返します。成否を知りたいときは PID を1つずつ渡します。
(sleep 0.3; exit 2) & a=$!
(sleep 0.6; exit 0) & b=$!
wait $a; echo "a=$?"
wait $b; echo "b=$?"
a=2
b=0
並列に動かすと合計時間が短くなる
time (for i in 1 2 3; do sleep 1 & done; wait)
real 0m1.035s
1秒かかる処理を3つ並列に動かして、合計は約1秒でした。順番に動かせば3秒かかるところです。
wait -n でどれか1つの完了を待つ
wait -n は、動いているバックグラウンド処理のうち、最初に終わった1つを待ちます(bash 4.3 以降)。
(sleep 0.9; exit 1) & (sleep 0.3; exit 5) & (sleep 0.6; exit 0) &
wait -n; echo "first=$?"
wait -n; echo "second=$?"
wait -n; echo "third=$?"
wait -n; echo "none=$?"
first=5
second=0
third=1
none=127
終わった順(0.3秒 → 0.6秒 → 0.9秒)に終了ステータスが返っています。待つ対象が残っていないときは 127 です。bash 5.1 以降では -p 変数名 で、終わった処理の PID も受け取れます。
(sleep 0.6; exit 1) & a=$!
(sleep 0.3; exit 5) & b=$!
wait -n -p done_pid
echo "rc=$? 終わったのは b か: $([ "$done_pid" = "$b" ] && echo yes || echo no)"
wait
rc=5 終わったのは b か: yes
同時に動かす数を制限する(コピペ用)
wait -n を使うと、「同時に2つまで」のような制限つきの並列実行が書けます。
#!/usr/bin/env bash
# 同時に2つまで動かす
max=2
job(){ sleep "$1"; echo "job $1 finished"; [ "$1" != 0.4 ]; } # 0.4 だけエラーを返す
fail=0
for t in 0.6 0.4 0.2 0.5; do
job "$t" &
if (( $(jobs -rp | wc -l) >= max )); then wait -n || fail=$((fail+1)); fi
done
while (( $(jobs -rp | wc -l) > 0 )); do wait -n || fail=$((fail+1)); done
echo "fail=$fail"
job 0.4 finished
job 0.6 finished
job 0.2 finished
job 0.5 finished
fail=1
jobs -rp は動いているバックグラウンド処理の PID を並べます。上限に達したら wait -n で1つ終わるのを待ち、エラーで終わった数を fail に数えています。
wait のエラーになる例
wait 999999; echo "rc=$?"
wait: pid 999999 is not a child of this shell
rc=127
wait で待てるのは、そのシェルが起動した子プロセスだけです。別の端末で起動した処理や、サブシェル ( ... ) の中で起動した処理は対象外です。$! もサブシェルやパイプの中で代入すると外側に伝わらないので、同じシェルの中で控えます。
| wait の書き方 | 待つ対象 | 終了ステータス |
|---|---|---|
wait |
すべてのバックグラウンド処理 | 常に 0 |
wait PID |
その PID の処理 | その処理の終了ステータス |
wait PID1 PID2 |
指定したすべて | 最後に指定した PID の終了ステータス |
wait -n |
最初に終わった1つ | その処理の終了ステータス(対象なしは 127) |
wait -n -p 変数 |
同上(PID を変数に入れる) | 同上(bash 5.1 以降) |
wait %1 |
ジョブ番号1 | そのジョブの終了ステータス |
シェル関数・パイプ・組み込みコマンドに timeout を掛ける
timeout は 外部コマンドを起動する仕組みなので、シェル関数や cd のような組み込みコマンドは直接は渡せません。
slow(){ sleep 5; echo done; }
timeout 1 slow; echo "rc=$?"
timeout 1 cd /tmp; echo "rc=$?"
timeout: failed to run command 'slow': No such file or directory
rc=127
timeout: failed to run command 'cd': No such file or directory
rc=127
bash -c で包む
slow(){ sleep 5; echo done; }
export -f slow
timeout 1 bash -c slow; echo "rc=$?"
timeout 1 bash -c "cd /tmp && pwd"; echo "rc=$?"
rc=124
/tmp
rc=0
関数は export -f 関数名 で子の bash に渡し、timeout 秒 bash -c 関数名 と書きます。関数が使う変数も export しておきます。
パイプ全体に掛ける
timeout 1 sleep 5 | cat; echo "PIPESTATUS=${PIPESTATUS[*]}"
timeout 1 bash -c "sleep 5 | cat"; echo "rc=$?"
PIPESTATUS=124 0
rc=124
timeout 1 A | B と書くと、時間制限が掛かるのは A だけです。$? はパイプの最後(B)の値になるので、A の結果は ${PIPESTATUS[0]} で確かめます。パイプ全体に掛けたいときは bash -c "A | B" で包みます。
逆に、パイプの後ろ側だけに掛けた場合はこうなります。
time (sleep 3 | timeout 1 cat); echo "rc=$?"
real 0m3.002s
rc=124
cat は1秒で打ち切られていますが、前側の sleep 3 が終わるまでパイプ全体は終わりません。全体の時間を抑えたいなら、やはり bash -c で包むのが確実です。
条件が満たされるまで待つ・リトライする
準備ができるまで上限つきで待つ
サービスの起動やファイルの生成を待つ定型です。until 条件; do sleep 間隔; done を timeout で包みます。
#!/usr/bin/env bash
# ready.flag ができるまで最大3秒待つ
(sleep 1.2; touch ready.flag) &
if timeout 3 bash -c 'until [ -e ready.flag ]; do sleep 0.2; done'; then
echo "準備完了"
else
echo "時間切れ rc=$?"
fi
rm -f ready.flag
if timeout 1 bash -c 'until [ -e ready.flag ]; do sleep 0.2; done'; then
echo "準備完了"
else
echo "時間切れ rc=$?"
fi
準備完了
時間切れ rc=124
条件の部分を nc -z localhost 8080(ポートが開いたか)や curl -fs URL(応答が返るか)に置き換えれば、そのまま起動待ちに使えます(この2つの置き換えは未実行です)。
リトライ関数(コピペ用)
1回あたりの制限時間と、最大回数を指定して繰り返す関数です。
#!/usr/bin/env bash
retry() { # retry 回数 制限秒 コマンド...
local max=$1 limit=$2 i rc; shift 2
for ((i=1; i<=max; i++)); do
timeout "$limit" "$@"; rc=$?
case $rc in
0) echo "成功(${i}回目)"; return 0 ;;
124) echo "${i}回目: ${limit}秒で打ち切り" ;;
*) echo "${i}回目: 終了ステータス $rc" ;;
esac
(( i < max )) && sleep $((i)) # 待ち時間を 1秒, 2秒, ... と伸ばす
done
return "$rc"
}
retry 3 1 sleep 3; echo "retry rc=$?"
retry 3 1 ls /nonexistent; echo "retry rc=$?"
1回目: 1秒で打ち切り
2回目: 1秒で打ち切り
3回目: 1秒で打ち切り
retry rc=124
ls: cannot access '/nonexistent': No such file or directory
1回目: 終了ステータス 2
ls: cannot access '/nonexistent': No such file or directory
2回目: 終了ステータス 2
ls: cannot access '/nonexistent': No such file or directory
3回目: 終了ステータス 2
retry rc=2
ポイントは3つです。
- 124(時間切れ)とそれ以外を分けて表示しているので、ログから原因を追いやすくなります。
- 待ち時間を回数に応じて伸ばすと、相手側が混み合っているときに負荷を掛けすぎずに済みます。
- 最後の試行のあとは sleep しないよう、
(( i < max ))で分けています。
timeout が使えない環境での代わりの書き方
sleep と kill と wait を組み合わせると、同じことができます。
#!/usr/bin/env bash
sleep 5 & pid=$! # 本体
( sleep 1; kill "$pid" 2>/dev/null ) & watcher=$! # 1秒後に止める見張り役
wait "$pid"; rc=$?
kill "$watcher" 2>/dev/null
echo "rc=$rc"
rc=143
1秒で打ち切られ、終了ステータスは 143(TERM で終了)になりました。timeout の 124 とは値が違う点だけ意識しておきます。
macOS で使うときのポイント
この節は macOS の実機がなく未実行です。一般に知られた仕様として整理しているので、手元の環境で確かめてから使ってください。
| 項目 | Linux(GNU) | macOS での扱い(未実行) |
|---|---|---|
| timeout | 標準で使える | 標準では入っていない。Homebrew で coreutils を入れると gtimeout として使える |
| sleep の小数 | 使える | 使える |
sleep の接尾辞・infinity |
使える | 版によって対応が異なるので man sleep で確認する |
wait -n |
bash 4.3 以降で使える | 標準の /bin/bash は 3.2 系のため使えない。Homebrew の bash か zsh を使う |
wait -n -p |
bash 5.1 以降 | 同上 |
両方の環境で動かすスクリプトでは、コマンド名を変数に入れておく書き方が便利です(未実行)。
if command -v timeout >/dev/null 2>&1; then
TIMEOUT=timeout
elif command -v gtimeout >/dev/null 2>&1; then
TIMEOUT=gtimeout
else
echo "timeout が見つかりません(macOS は brew install coreutils)" >&2
exit 1
fi
"$TIMEOUT" 10 ./job.sh
書く前のチェックリスト
| 確認すること | 見るポイント |
|---|---|
| 時間切れを 124 で判定しているか | $? はすぐ変数に控える |
TERM に応じない処理に -k を付けたか |
付けないと最後まで動くことがある |
| コマンド自身の終了ステータスが必要か | 必要なら --preserve-status |
| 端末の入力を読むか | 読むなら --foreground |
| 関数・パイプ・組み込みコマンドか | bash -c で包み、関数は export -f |
パイプの $? を見ていないか |
${PIPESTATUS[0]} を使う |
$! をすぐ変数に控えたか |
次の & で上書きされる |
| 引数なしの wait で成否を見ていないか | 常に 0。PID を渡すか wait -n を使う |
| リトライに上限と待ち時間があるか | 回数の上限と sleep を入れる |
よくある質問
Q. timeout の終了ステータス 124 と 137 は何が違いますか?
A. 124 は「時間切れで TERM などのシグナルを送った」ことを表します。137 は 128+9 で、KILL シグナルで終了したことを表します。-k を付けて KILL まで進んだときや、-s KILL を指定したときに 137 になります。
Q. timeout を掛けたのに処理が止まりません。どうすればよいですか?
A. 既定で送られる TERM に応じない作りの処理は動き続けます。timeout -k 5 10 コマンド のように -k を付けると、TERM の5秒後に KILL が送られて確実に止まります。本文の stubborn.sh の例では、-k なしで約6秒、-k 1 ありで約3秒でした。
Q. シェル関数に timeout を掛けるにはどうすればよいですか?
A. timeout は外部コマンドを起動するので、関数名をそのまま渡すと 127 になります。export -f 関数名 で関数を渡し、timeout 10 bash -c 関数名 と書きます。
Q. wait と sleep はどう使い分けますか?
A. 「処理が終わるまで」待つなら wait、「決まった時間だけ」待つなら sleep です。終わる時間が読めない処理を sleep で待つと、長すぎたり短すぎたりします。バックグラウンドで動かした処理は $! で PID を控えて wait するのが確実です。
まとめ
timeout 秒 コマンドで時間制限。時間切れは 124、起動できないときは 126・127、timeout 自身のエラーは 125- TERM に応じない処理には
-k。指定秒数のあとに KILL を送り、終了ステータスは 137 になる --preserve-statusでコマンド自身の終了ステータスを受け取れる。--foregroundは端末の入力を読むコマンド向け- sleep は小数・接尾辞(s・m・h・d)・複数指定に対応。
sleep infinityは止められるまで待つ $!をすぐ変数に控えてwait "$pid"。引数なしの wait は常に 0 を返すwait -nで最初に終わった1つを待つ。同時実行数の制限に使える- 関数・パイプ・組み込みコマンドは
bash -cで包む。リトライは timeout と sleep を for ループで組み合わせる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linux timeout・sleepコマンドの使い方 時間制限と待機の書き方」
株式会社RJCでは、Linuxコマンドやシェルスクリプトの書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。