メインコンテンツへスキップ

Bashスクリプトで時間がかかりすぎないように timeout を使う TIL

·5 分
2025/05 Bash シェルスクリプト Linux Timeout コマンド

Bashスクリプトで時間がかかりすぎないように timeout を使う TIL

引用元:https://news.ycombinator.com/item?id=44096395

majke 2025/05/26 13:55:13

straceでシステムコールの失敗をテストする裏技、超お気に入りなんだ。
例えばこんな感じ→ strace -e trace=clone -e fault=clone:error=EAGAIN
参考リンクも貼っとくね。

jonhohle 2025/05/26 16:35:44

これ、マジやばいね!もっと早く知りたかったよー。今まで失敗パターンをテストできなくて、無理やりスタブで逃げてたんだけど、範囲を限定するのに必死だったんだ。
サンキュー!

eru 2025/05/27 07:59:49

これも好きかもね。
https://github.com/eradman/entr

dwattttt 2025/05/26 21:18:28

WindowsだとApplication Verifierでフォールトインジェクションとか色々できるよ。(リンク略)
ただ、これはネイティブコード専用なんだ。

colejohnson66 2025/05/27 14:46:49

.NETみたいなマネージドアプリだったら、たぶん依存性注入(DI)がいい方法だよ。
失敗するかもしれないメソッドがあるコンポーネントをテストしたいなら、インターフェースのラッパー書いて、失敗するモックを注入するんだ。
JITが普通のアプリ使い方だといい感じに最適化してくれるよ。

broken_broken_ 2025/05/27 05:06:30

Dtraceも同じようなこと、いやもっとすごいこと(破壊的アクションとか)できると思うよ。しかもWindowsもサポートしてるんだ。

0xbadcafebee 2025/05/27 00:35:17

ヘルスチェックでは、タイムアウト時間と最大リトライ回数の両方を設定するのが理想だよ。
ネットワークの問題を考えて、リトライ回数と時間で失敗を判断する方が、ただ待つより早く終わるよね。
Bashスクリプトで書く例もあるけど、curlには元々リトライ機能が付いてるから、そっちを使うのが楽ちんだよ。
–retryとか–retry-max-timeオプションで設定できるんだ。

0xbadcafebee 2025/05/27 19:25:39

(追記)さっきのスクリプト例、「done」の後に「exit 1」が必要だった!そうしないと最大ループ数超えてもエラーにならないからね。
あと、curlの「–retry 300」はスクリプトと合わせるなら「–retry 5」にすべきだったわ。ごめん!

robinhouston 2025/05/26 19:56:06

Macにtimeoutコマンドがないからさ、bashの組み込みだけでtimeout作れないかなーって試行錯誤してたんだ。全部組み込みは無理だったけど、sleepコマンド(大体どこでも使えるはず)を許すなら、これでいけると思うよ。

コードはこんな感じ。
サブシェルでsleepして時間になったらメインシェルにSIGALRMを送るんだ。
メインシェルが終了する前にサブシェルを終了させるtrapも設定してるよ。
timeout関数でALRMシグナルが来たときの処理(指定コマンド)を設定して_alarmを呼ぶんだ。

実行例では10秒後に’TIME OUT!’って表示されて終了するようになってる。
ちなみに例のループは20秒かかるからタイムアウトするよ。

ryao 2025/05/27 01:15:49

これさ、俺が12年前にStack Overflow見てやったやつがあるんだよ。GitHubのコードなんだけどね。
それを使えば(helpers除く)、こんな感じで書けるんだ。
timeoutをコールして、成功したか(タイムアウトしなかったか)をif文でチェックするんだ。
君のやつみたいに、俺も組み込みコマンドとsleepだけだよ。
busybox ashでも動くようにPOSIX準拠目指したんだ({1..20}はbashismだけど)。
俺の工夫はさ、タイムアウトしたらfalseを返すようにしたこと。
そしたらエラー処理がメインスクリプト内で簡単にできると思ってね。

khc 2025/05/27 02:37:53

俺は13年前にread -t使って書いたことあるよ。GitHubのこれね
https://github.com/kahing/bin/blob/master/timeout.sh

timewizard 2025/05/27 07:08:50

こんなシンプルなのではどうかな?
command & sleep timeout; kill -SIGALRM %1

robinhouston 2025/05/27 09:35:59

それはまあ良いんだけど、もし既にバックグラウンドジョブが動いてたら、%1は間違ったジョブを指しちゃうよ。
%%ってのも使えるけど、コマンドがタイムアウト前に終わっちゃったら、これまた間違ったジョブを指すことになるね。

fpoling 2025/05/27 12:53:15

これはさ、コマンドが終わっても、タイムアウト時間分は必ずsleepしちゃうね。

craigds 2025/05/26 17:52:39

ちなみにさ、curlには–retry-connrefusedってフラグがあるんだよ。
これ使うと、シェルでこのリトライ処理を自分で書かなくて済むから便利だよ。

aidenn0 2025/05/26 15:05:51

あのね、もしbash -cで変数渡す必要があるなら、一番良い方法は引数として追加することだよ。
例えば、bash -c ’…’ – ”$1” ”$2”’ – ”$var1” ”$var2”ってやるんだ。
–を使うのが俺は好きなんだけど、最初の引数(argv[0])は$@で展開されないから、他の何かを使った方が分かりやすいと思う。
bashのprintf %qもあるけど、劇的に綺麗にならないならBourne互換を優先するかな。

AdieuToLogic 2025/05/27 03:49:16

> 俺は–を使うのが見た目好きなんだけど…
二重ハイフン(–)は、bashとかほとんどのUnix/Linux CLIプログラムにとって、すごく特別な意味があるんだよ。
getopts(1p)のmanページにも書いてあるけど、これはオプションの終わりを示すんだ。
オプションの終わりを示すもの:
- 最初に出てくる–引数で、オプション引数ではないもの
- オプション引数ではなく、かつ’-’で始まらない引数
- エラー
ほらね。

aidenn0 2025/05/27 16:54:25

「見た目が好き」って言ったのはそういうこと。実際はさ、bash -c だとそうは動かないんだよ(コマンド文字列の後の引数は全部 argv に入るんだ、0からね)。でもさ、そういう風に動く他のコマンドと”馴染む”感じなんだよね。

AdieuToLogic 2025/05/28 03:10:33

> 「見た目が好き」って言ったのはそういうこと。実際はさ、bash -c だとそうは動かないんだよ(コマンド文字列の後の引数は全部 argv に入るんだ、0からね)。でもさ、そういう風に動く他のコマンドと”馴染む”感じなんだよね。

君の例の – はそうやって動いてないよ。bash -c ’some command ”$1” ”$2”’ – ”$var1” ”$var2”

この例だと – は $0 に割り当てられるけど、それと同時にコマンドラインスイッチの解釈を止める役割もあるんだ。例えば bash -c ’/bin/ps ”$1”’ – ”-l” は期待通りに動くけど、bash -c ’/bin/ps ”$1”’ ”-l” はそうならないよ。

aidenn0 2025/05/28 14:48:49

> この例だと – は $0 に割り当てられるけど、それと同時にコマンドラインスイッチの解釈を止める役割もあるんだ。例えば…

それは bash の manページとも俺のテストとも違うよ。一番簡単なテストはこれ。 bash -c ’false; echo hi’ -e は ”hi” って表示するけど、 bash -e -c ’false; echo hi’ は期待通り。bash -c ’/bin/ps ”$1”’ foo ”-l” と bash -c ’/bin/ps ”$1”’ – ”-l” は同じように動く。

AdieuToLogic 2025/05/30 00:03:24

あー、君の言う通りだわ。俺の例で見られた変な挙動は bash とか -c ”コマンド文字列” の後の引数とは関係なかった。代わりに、俺が使ってるプラットフォームの ps が /bin/ps ’’ って呼び出された時にどう動くか、それが原因だったよ。

fragmede 2025/05/26 16:36:45

Busybox はさ、argv[0] を見て何を実行するか判断するんだ。だから argv[0] として ”ls” を食わせてやると ”ls” が動くってわけ(とか ”mv” とか ”cp” とか)。

miduil 2025/05/26 13:48:09

俺がよくやるリトライ処理はこうだよ。
for i in {0..60}; do
true – ”$i” # shelleck surpression
if eventually_succeeds; then break; fi
sleep 1s
done

あんまりエレガントじゃないけど、まあまあ正しいかな。次のレベルは指数バックオフだね。だいたいちょっとだけ組み合わせやすさが残る感じ。

mdaniel 2025/05/26 14:42:00

君次第だけど、Shellcheck がその問題を解決してほしいやり方は、for _ in みたいに _ を使うことだと思うよ。

https://github.com/koalaman/shellcheck/wiki/SC2034#intention

miduil 2025/05/26 14:31:13

アプリケーションによっては eventually_succeeds に timeout がまだ必要になることに注意ね。Bashとか、まあ POSIX/IO/Processes を扱うときはいつでもそうだけど、防御的なコーディングをしないとダメだよ。何やっても結果は伴うんだから。

xx__yy 2025/05/27 03:21:35

俺はこの解決策の方が好きだな。文字列コマンドを実行する bash がないのが良いね。eventually_succeeds に簡単に timeout をつけられるし。

noufalibrahim 2025/05/26 14:35:15

前はさ、timeout 1800 mplayer show.mp4 ; sudo pm-suspend

ってのを子供が小さい頃、30分だけ自動で番組を見せるための貧乏人のペアレンタルコントロールとして使ってたんだ。便利なコマンドだよね。

noufalibrahim 2025/05/27 04:25:20

どっかでよんだけど、「シェルがあれば、どうにかなる」ってさ.ちょっとふざけてるけど、まあホント.こういうシンプルなコマンドとプログラマブルなシェルがくれる力はでっかいし、どれだけきょうちょうしてもたりないくらいだよ.

epr 2025/05/26 16:38:40

コマンドをちょくせつかくより、かんすうをtimeoutでじっこうできるラッパーをつかうのがすきだな.ふくざつなしょりもかんすうにいれられるしべんりだよ.かんたんなれいコードをのせとくね.(コードは省略)

abbeyj 2025/05/26 20:35:44

まえのコメントのコードだけど、さいごのほうの$@は”$@”にしないとスペースいりのひきすうがうまくいかないよ.ためしてみてね.(コード例は省略)

もっとコメントを表示(1)
epr 2025/05/27 11:11:59

ああ、そのタイポまちがいだわ.シェルスクリプトで”$@”ってしょっちゅうかくのに、もっとちゃんとわかってるべきだった.shellcheckでもみつけられただろうしね.でも、HNのへんしゅうじかんすぎちゃったから、まちがいがのこったままだな

pveierland 2025/05/26 13:59:45

ちょいまえに、Kubernetesのセットアップでコマンドのtimeoutしょりをまさにいれたところだよ.このPOSIXシェルスクリプトでできたawait-cmd.shとかawait-http.shとかawait-tcp.shは、こなれてるし、いくつかのシナリオですごくべんりだよ.GitHubのリンクも貼っとくね:https://github.com/vegardit/await.sh

frou_dh 2025/05/26 13:54:23

そういえば、timeoutコマンドってGNU Coreutilsのいちぶらしいね.きじをよんだあとに、Bashじたいのいちぶなのかはっきりしなかったんだ.

mdaniel 2025/05/26 14:45:55

あと、きをつけてほしいんだけど、いろんなものとおなじで、timeoutコマンドとそのひきすうは、/usr/bin/timeoutとかBrewのgtimeoutとか、かんきょうによってちがうんだ(gプレフィックスはそこからきてる).BSDではどうなってるか、つかったことないからわかんないけど.

aidenn0 2025/05/26 15:07:08

GNU Coreutilsにgプレフィックスをつけるのは、ほとんどの非Linux Unixシステムでよくあることだよ.ベースシステム(gmakeとかgtarとか、makeとかtarとか)とのコンフリクトをふせぐためなんだ.

jonhohle 2025/05/26 16:48:30

でも、それもまためんどくさいんだよね.gプレフィックスつきのバージョンはLinuxシステムにはインストールされてないから、それらにいぞんしてるスクリプトはポータブルじゃなくなるわけ.

mdaniel 2025/05/26 17:07:06

Thankfully bash は tolerates それ、if the script author cares なら、e.g. gnu_sed=gsed
if ! command -v $gnu_sed; then
gnu_sed=$(detector_wizardry)
fi
$gnu_sed -Ee … って。

chasil 2025/05/26 15:11:24

> It’s a shame we can’t use timeout with until directly。The until keyword は part of the POSIX.2 shell specification で、timeout functionality は include してないんだ。bash に implemented することはできるけど、他の shells(Debian dash が the main concern)には portable じゃない。This is the reason that it is implemented as a separate utility。Search for ”The until loop” below to see the specification。https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V…

blueflow 2025/05/26 16:15:13

Of course I’m very judgemental towards people who ignore the established knowledge of the field and instead rely on social hearsay。Call me old and grumpy。

couscouspie 2025/05/26 16:32:02

To be fair、manual で tool を learning するのは not very efficient。個人的には most utilities の manuals は detail に lacking で、reading だけだと理解 difficult。特に needing してない functionality の reading は waste of time みたいに feels する。everything Vim を fluent な人が manual once or twice でそうなったとは doubt。

blueflow 2025/05/26 16:39:17

Furthermore、reading about functionality は waste of time みたいに feels する

yeah、but simplified、this is how you end up with programmers that write an if statement 10 times because they don’t know what a for loop is。The waste of time still happens elsewhere。

t-3 2025/05/26 16:48:32

> I doubt anybody who is fluent in everything Vim has to offer learned that by reading through the manual once or twice。The vim manual only explains the flags and how/where to access the documentation and tutorial。You don’t read it to ”become fluent” in using the TUI application、you read it to learn about how to run the program from the commandline。

jonhohle 2025/05/26 16:49:51

If you haven’t、look at the FreeBSD man pages、when possible(most things in coreutils)。They are infinitely better than the GNU versions。

johnisgood 2025/05/26 16:52:36

OpenBSDのmanページもおすすめだよ。
ちなみにLinuxだと、全部のmanページ(たとえばPOSIXとかGNU、Linuxとか)読みたいから、”man-all”って”man -a”にエイリアスしてるんだ。

PeterWhittaker 2025/05/26 14:51:32

timeoutコマンドじゃなくて、自分でシェル関数作る方法もあるよ。何やってるか見せるためにjobsとかecho入れてるけど、基本的にはバックグラウンドで実行して、指定時間待つか終わるかで見張って、時間切れならkillする感じ。
オプションでtimeoutとかsleep時間も変えられるよ。

oso2k 2025/05/26 22:13:10

他にも面白いテスト方法として、純粋なbash(たぶん15年くらい前のバージョンで修正必要かも)で”timeout 5 bash -c ’cat < /dev/null > /dev/tcp/google.com/80’”みたいにTCP接続テストできるよ。
google.comとかポート80はテストしたいとこに変えてね。
サーバーが起動してなかったり、ファイアウォールとかプロキシがあるとエラーになるかタイムアウトするよ。

halJordan 2025/05/27 02:56:56

timeoutってbashの外部プログラムなんだよね。

minaguib 2025/05/26 14:46:12

この記事みたいにcurl使うなら、プロセスレベルでタイムアウト管理するより、curl自体に”-m”オプションでタイムアウトさせる方がいいかもね。

aidenn0 2025/05/26 15:00:47

いや、記事はcurlをループで呼んでるから、単に”-m”でcurlにタイムアウトさせるだけじゃダメなんだ。
ループ全体にタイムアウトが必要なんだよ。
curlにもリトライとかの設定である程度の時間続けるオプションがあるかもだけど、パッと出てこないな。

pvtmert 2025/05/27 22:10:00

curlにはすでにそういうのをハンドリングするたくさんのオプションがあると思うよ。
いい解説付きのドキュメントも色々あるし。
たとえばこれとか→https://everything.curl.dev/usingcurl/downloads/retry.html
たぶんこの記事の前提は、findみたいに自分でタイムアウト機能持ってない他のプロセスにも適用できるってことだと思うな。

arjie 2025/05/26 17:43:02

最近友達がhttps://google.github.io/zx/api見せてくれたんだけど、これめっちゃ使うの楽しいよ。
シェルにすごく近くて、LLMも結構よく知ってるんだって。

jiehong 2025/05/26 21:25:57

あー、これ聞くとJavaScriptのbunのshell APIを思い出すな。
[0]: https://bun.sh/docs/runtime/shell

chii 2025/05/26 13:47:55

sleep 回数を数えて、その回数を until 条件に入れれば終了できるんじゃない? timeout 使わなくてもいいし、timeout のために別の bash をサブシェルで起動する必要もないよ。

xelxebar 2025/05/26 13:57:01

筆者の目的には合ってると思うよ。ただ、curl はサーバーの状態によってはタイムアウトに時間がかかることがあるんだよね。応答やエラーまでの最大時間を保証するにはどうやるか知りたいな。

cb321 2025/05/26 17:09:26

POSIX じゃないけど、bash や zsh には時間の組み込み機能があるんだ(date コマンドより古いけどね)。EPOCHSECONDS を使ってタイムアウトを実装する例とか、より精度の高い EPOCHREALTIME を Zsh で使う方法とかを紹介してるよ。これらの変数は ”under known” (あまり知られてない)みたいだね。

chii 2025/05/26 14:14:12

curl の場合はリクエストや接続のタイムアウトパラメータがあるから、それでサーバーが生きてるか確認できるよ。でも、そういうオプションがないコマンドなら、やっぱり timeout ユーティリティを使うのが理にかなってるね。

diggan 2025/05/26 14:28:19

curl には –connect-timeout と –max-time ってオプションがあるよ。でも、これらが DNS ルックアップとか TLS ハンドシェイクも考慮するのか覚えてないんだよね。回線が不安定なときはこれらのタイムアウトだけだと難しい場合があって、結局ハードリミットをかけるなら curl をラップする必要が出てくるみたい。

SoftTalker 2025/05/26 16:31:54

Linux でプロセスが完全に固まって kill できないことが本当によくあったんだ。だいたい I/O 待ちが原因みたいだけど。

chgs 2025/05/27 07:06:49

そういうケースだと kill -9 でも助けにならないんだよね

crabbone 2025/05/26 16:21:53

そんなに簡単じゃないね。curl がハングアップしない保証はないから。ちゃんとやるならループの前に別プロセスで親を監視するコードが必要になる…でも、正直 Bash でそこまでやりたくないよね。curl がハングしないと仮定するなら、タイムスタンプ比較の方が回数カウントより timeout のエミュレーションとしては良いかな。あとは指数バックオフとか色々やりたくなるだろうけど…やっぱ Bash じゃない方がいいだろうね。

もっとコメントを表示(2)
sllabres 2025/05/26 18:43:04

個人的な経験から言うと、リトライが何回必要だったか出力するのをおすすめするよ。ゼロを期待してるなら特にね。
そうしないと、リトライループが不安定なサービスやネットワークの問題を隠しちゃうことがあるんだ。

febusravenga 2025/05/26 18:10:15

昔、timeout.shを自分で作ったんだけど、めっちゃ複雑になっちゃったんだよね。関数呼び出しとか多くなると、環境を引き継ぎたいからさ。制御プロセスとかsleepプロセスとかあって、どっちが先に終わるかの競争とかあってさ…。たぶん、だから組み込みのtimeoutを無視したんだろうな…。
https://github.com/zbigg/bashfoo/blob/master/timeout.sh

broken_broken_ 2025/05/27 05:11:04

これ読んで、前に書いた自分のブログ記事思い出したわ。そこで”timeout”にも触れてるんだ。
https://gaultier.github.io/blog/way_too_many_ways_to_wait_fo
これは、シェルじゃなくて他のプログラミング言語で実装する場合とか、内部の仕組みを知りたい場合に役立つかもね。

AtlasBarfed 2025/05/26 14:44:53

bashより標準ライブラリが整ってない言語なんてあるのか?
bashスクリプト用のちょっとモダンな標準ライブラリを作ろうとしてるとこ、Stack Overflow以外にある?

t-3 2025/05/26 16:56:09

Busyboxとかcoreutilsとか、お前のプラットフォームのユーザースペースが“標準ライブラリ”だよ。
シェルは基本、制御フローとIOのためにあるだけで、それ以外は全部コンピューター上のプログラムなんだ。

alganet 2025/05/26 19:28:28

shellfire-devとかshellspecとかoils.pubとか、bashのモダンな試みはいくつかあるよ。他にもあるだろうね。シェルは幅広いユーザーがいるから、目標次第で深掘りできるポイント(インタプリタ間の移植性、他のバイナリへの依存度、ブートストラップの早い段階で動くかとか)はたくさんあるよ。俺のプロジェクトはこれ。
https://github.com/alganet/coral
https://github.com/alganet/shell-versions
https://github.com/Mosai/workshop

cpach 2025/05/26 15:03:18

そこまで複雑になるなら、ほんとに頑張る価値ある?個人的には、そこまでいくとPythonとかRubyみたいな、もっと有能な言語使う方がいいと思うな。

ninkendo 2025/05/26 15:20:38

それだけじゃなくて、bashの強みはどこにでもあることだよ。(あるいは、もっと普遍性を求めるならposix shだね。)もしbashにたくさん機能を追加し始めたら、使う場所全部に十分新しいバージョンが入ってるって確信できないと使う意味ないじゃん。それって、そもそもbashの主な使い道(俺の意見だけど、どんなUnix系システムでも動く移植可能なスクリプト)の目的を台無しにしちゃうんだよな。

crabbone 2025/05/26 16:27:26

PythonもRubyも、並行処理とか直列化とか、簡潔さのインターフェースがシンプルじゃないんだよ。全然だめ。それに、望ましい方向には動いてないしね。Perlなら挑戦できたかも…でもそれは別の問題だし、それでもまだ完璧じゃない。
PowerShellはもったいない機会だったな。底なしの金庫を持つ会社がリソースを大量に投入したプロジェクトなのに…結局イマイチだった。 sensibleな代替がないかと思ったけど、まだ見つかってないわ。

beej71 2025/05/26 18:56:59

POSIXとSingle Unix Specificationが、まあ全部だね。
俺はシェルスクリプトたくさん書くけど、POSIX準拠にすることが多いよ。依存関係については、commandコマンドを使えば、インストールされてなくても gracefullyに失敗させられる。

oweiler 2025/05/26 15:31:17

Bashには標準ライブラリってなくて、組み込みコマンドと外部コマンドだけなんだよね。外部コマンドはただのツールって感じ。

queuebert 2025/05/26 19:14:05

’[’ (テストコマンド)でさえ、だいたい/usr/binにある外部バイナリなんだよ。

AStonesThrow 2025/05/26 21:06:41

Bashの”標準ライブラリ””組み込みコマンド”、”予約語”について詳しく教えてくれてるね。組み込みコマンドはfork()/exec()なしで使える内部的なもの。予約語はループとか制御に使うキーワードだよ。多くのディストリビューションには.bashrc があって、これは標準ライブラリみたいに使えるって人もいるみたい。’[’ (テストコマンド)も、組み込みコマンドとして解釈されないスクリプトのために外部バイナリとしても存在するんだって。通常は組み込みとして使われるよ。

eemil 2025/05/27 11:13:47

Bashの変なとこを知るたびにPowershellとかPythonに行きたくなるなー。Bashってマジでハッキーだよね。他の言語なら文法だけ考えればいいけど、Bashは「どうやったらやらかさないか?」って考えちゃうんだよね。複雑な処理は特にさ。

lzy 2025/05/27 02:20:34

記事のアイデア、いいね! 本番環境でサイレントタイムアウトで困ったことあるよ。これってネストした非同期呼び出しとか、よくわかんない外部依存関係とか、どう対応するのかな? ログツールと連携できるともっと見やすくなりそう!

tryauuum 2025/05/26 16:01:54

なんで timeout --signal=SIGKILL を使わないで、extra bashでラップして殺しやすくしたの?..

teo_zero 2025/05/26 22:00:53

記事によると、プロセスしか殺せないから、組み込みコマンドの”until”は新しいプロセスを作らないし、ダメなんだって。

js2 2025/05/26 17:35:26

retry っていう便利なツールがあって、リトライ処理が楽になるんだよ:https://github.com/minfrin/retry

exo762 2025/05/27 11:34:16

僕の.bashrcにこれ入れてるよ:
function retry {
until $@; do :; done
alert
}
export -f retry
スクリプト以外ならけっこう使えるね。

記事一覧へ

海外テックの反応まとめ
著者
海外テックの反応まとめ
暇つぶしがてらに読むだけで海外のテックニュースに詳しくなれるまとめサイトです。