Bashで2つのプロセスを作成しました。これら2つのプロセスは相互依存的です。どちらかが死んだら、どちらもやめたいです。最もきれいな方法は何ですか?現在私は以下を持っています:
# start process a
/bin/program_a;
a_pid=$!
# start process b
/bin/program_b;
b_pid=$!
# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid
ただし、これはプロセスaが終了した場合にのみプロセスbを終了するのに役立ちます。プロセスbが終了したら、プロセスを終了する方法は?
答え1
そしてzsh
:
pids=()
trap '
trap - CHLD
(($#pids)) && kill $pids 2> /dev/null
' CHLD
sleep 2 & pids+=$!
sleep 1 & pids+=$!
sleep 3 & pids+=$!
wait
(ここではsleep
テストコマンドとして使用されます)
bash
CHLDトラップは、このオプションがオンのときにのみ実行されるようです。m
このオプションでは、ジョブは別のプロセスグループで実行されるため、ジョブを開始したくありません。また、ハンドラ内でハンドラをリセットすることはbashでは機能しないようです。したがって、bash
これに対応する内容は次のとおりです。
pids=()
gotsigchld=false
trap '
if ! "$gotsigchld"; then
gotsigchld=true
((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
fi
' CHLD
sleep 2 & pids+=("$!")
sleep 1 & pids+=("$!")
sleep 3 & pids+=("$!")
set -m
wait
set +m
答え2
私がテストしたシェルの中で私が知っている限り、3つのシェルは、と、との面SIGCHLD
でほぼ正しいことをします。ご覧のとおり、信号ハンドラを設定するときに割り込みが可能でなければなりません。wait
yash
dash
mksh
wait
wait()
、ㅏsleep()
またはread()
持ち運べるsleep()
(もちろん、以前の呼び出しで割り込みが発生した場合は奇妙に動作しますが、alarm()
)。どの(ブロックされていない/無視される)信号はwait()
。
私の考えでは、これらのもののシェル実装はそれほど変わらないはずですが...いくつかは異なります。特に、、、、、、bash
の最悪の様子を見せてくれます。そしてbash
ksh93
dash
mksh
yash
zsh
zsh
ksh93
ほぼ次のシーケンスは正しく実行されますが、最初の終了プロセスの終了状態は維持されません。ひどいものではありません。最後に終了したPIDをzsh
使用するように求められたことについて不平を言うこともあります。wait
これが私がしたことです:
unset IFS
script=$(cat <<""
PS4="$0 + "
trap ' for p ### loop over bgd pids
do shift ### clear current pid
if kill -0 "$p" 2>/dev/null ### still running?
then set -- "$@" "$p" ### then append again
else wait "$p" ### else get return
exit "$(kill "$@")$?" ### kill others; exit
fi
done' CHLD ### wait til CHLD
for n in $(shuf -i 3-7) ### randomize order
do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
done; set -x; wait ### debug, wait
)
上記のコードは、シェルが返されるとすぐに残っているすべてのバックグラウンドサブシェルを終了するだけでなく、最初の返されたサブシェルの終了コードを親シェルの終了コードに伝播する必要があります。それしなければならないwait
まだ待機していない子プロセスを呼び出すと、バックグラウンドプロセスの終了ステータスがすぐに返される必要があるために機能します。 SIGCHLDは最初の信号を終了する信号なので、wait
2番目の信号をwait
表示する必要があります。最初最初に返された子孫が待たなければならない実際の時間。少なくとも簡単にはそうする必要があります。しかし、シェル実装が複雑になるほど、このロジックの信頼性は低下するようです。
$script
これを行うときに各シェルを実行する方法は次のとおりです。
for sh in yash zsh ksh bash mksh dash
do time "$sh" +m -c "$script" ### no job control
done
bash
3秒以内に終了しない唯一のシェルです。zsh
そしてksh93
両方(私の考えではこれは間違っているようです) exit 0
、そうでない場合は、3秒以内に終了してください。exit 3
3秒以内に他のもの。テスト結果は次のとおりです。
yash + wait
yash + shift
yash + wait 19111
yash + kill 19112 19113 19116 19117
yash + exit 3
real 0m3.013s
user 0m0.007s
sys 0m0.000s
zsh + wait
zsh + p=19124
zsh + shift
zsh + kill -0 19124
zsh + set -- 19125 19127 19129 19132 19124
zsh + p=19125
zsh + shift
zsh + kill -0 19125
zsh + wait 19125
zsh:wait:12: pid 19125 is not a child of this shell
zsh + kill 19127 19129 19132 19124
zsh + exit 0
real 0m3.023s
user 0m0.017s
sys 0m0.000s
ksh + wait
ksh + shift
ksh + kill -0 19137
ksh + 2> /dev/null
ksh + set -- 19138 19139 19140 19141 19137
ksh + shift
ksh + kill -0 19138
ksh + 2> /dev/null
ksh + wait 19138
ksh + kill 19139 19140 19141 19137
ksh + exit 0
real 0m3.018s
user 0m0.000s
sys 0m0.010s
bash + wait
real 0m7.018s
user 0m0.007s
sys 0m0.007s
mksh + wait
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19157
mksh + set -- 19158 19159 19160 19161 19157
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19158
mksh + set -- 19159 19160 19161 19157 19158
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19159
mksh + set -- 19160 19161 19157 19158 19159
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19160
mksh + set -- 19161 19157 19158 19159 19160
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19161
mksh + wait 19161
mksh + kill 19157 19158 19159 19160
mksh + exit 3
real 0m3.022s
user 0m0.003s
sys 0m0.000s
dash + wait
dash + shift
dash + kill -0 19165
dash + set -- 19166 19168 19170 19173 19165
dash + shift
dash + kill -0 19166
dash + wait 19166
dash + kill 19168 19170 19173 19165
dash + exit 3
real 0m3.008s
user 0m0.000s
sys 0m0.000s