web-dev-qa-db-ja.com

すべてのサブシェルプロセスを終了するにはどうすればよいですか?

負荷がかかった状態でサーバーがどのように動作するかをテストするbashスクリプトがあります。

num=1
if [ $# -gt 0 ]; then
    num=$1
fi
for i in {1 .. $num}; do
    (while true; do
        { time curl --silent 'http://localhost'; } 2>&1 | grep real
    done) &
done        

wait

Ctrl-Cを押すと、メインプロセスは終了しますが、バックグラウンドループは実行され続けます。それらをすべて終了させるにはどうすればよいですか?または、並列に実行される構成可能な数のロジックループを生成するためのより良い方法はありますか?

30
ykaganovich

より簡単な解決策は次のとおりです。スクリプトの先頭に次の行を追加するだけです。

trap "kill 0" SIGINT

殺害0現在のプロセスグループ内のすべてのプロセスにシグナルを送信します。

45
Russell Davis

サブシェルを殺すが、自己を殺さない1つの方法:

kill $(jobs -p)
4
Craig McQueen

ジョブ制御 を使用する必要がありますが、残念ながら少し複雑です。これらが実行されると予想される唯一のバックグラウンドジョブである場合は、次のようなコマンドを実行できます。

_jobs \
  | Perl -ne 'print "$1\n" if m/^\[(\d+)\][+-]? +Running/;' \
  | while read -r ; do kill %"$REPLY" ; done
_

jobsは、すべてのアクティブなジョブ(実行中のジョブと最近終了または終了したジョブ)のリストを次のような形式で出力します。

_[1]   Running                 sleep 10 &
[2]   Running                 sleep 10 &
[3]   Running                 sleep 10 &
[4]   Running                 sleep 10 &
[5]   Running                 sleep 10 &
[6]   Running                 sleep 10 &
[7]   Running                 sleep 10 &
[8]   Running                 sleep 10 &
[9]-  Running                 sleep 10 &
[10]+  Running                 sleep 10 &
_

(これらは、_for i in {1..10} ; do sleep 10 & done_を実行して起動したジョブです。)

_Perl -ne ..._は、Perlを使用して実行中のジョブのジョブ番号を抽出しています。必要に応じて、明らかに別のツールを使用できます。 jobsの出力形式が異なる場合は、このスクリプトを変更する必要がある場合があります。ただし、上記の出力はCygwinにもあるため、おそらくあなたの出力と同じです。

_read -r_は、標準入力から「生の」行を読み取り、それを変数_$REPLY_に保存します。 _kill %"$REPLY"_は_kill %1_のようなものになり、ジョブ番号1を「強制終了」(割り込み信号を送信)します(_kill 1_と混同しないでください。これはprocessnumber 1.)一緒に、_while read -r ; do kill %"$REPLY" ; done_はPerlスクリプトによって出力された各ジョブ番号を調べ、それを強制終了します。

ちなみに、ブレース拡張はbeforeパラメータ拡張で処理されるため、_for i in {1 .. $num}_は期待どおりに機能しません。 _for i in "{1" .. "$num}"_と同等です。 (とにかく、ブレース拡張内に空白を含めることはできません。)残念ながら、私はクリーンな代替手段を知りません。 for i in $(bash -c "{1..$num}")のようなことをしなければならないと思います。さもなければ、算術for- loopなどに切り替えます。

また、ちなみに、whileループを括弧で囲む必要はありません。 _&_により、ジョブはすでにサブシェルで実行されます。

2
ruakh

これが私の最終的な解決策です。配列変数を使用してサブシェルプロセスIDを追跡し、Ctrl-C信号をトラップしてそれらを強制終了しています。

declare -a subs #array of subshell pids

function kill_subs() {
    for pid in ${subs[@]}; do
        kill $pid
    done
    exit 0 
}

num=1 if [ $# -gt 0 ]; then
    num=$1 fi

for ((i=0;i < $num; i++)); do
    while true; do
       { time curl --silent 'http://localhost'; } 2>&1 | grep real
    done &

    subs[$i]=$! #grab the pid of the subshell 
done

trap kill_subs 1 2 15

wait
0
ykaganovich

少し遅い答えですが、私にとっては_kill 0_やkill $(jobs -p)のような解決策は行き過ぎです(すべての子プロセスを強制終了します)。

1つの特定の子プロセス(およびそれ自体の子)が整理されていることを確認したい場合は、次のように、サブプロセスのPIDを使用してプロセスグループ(PGID)ごとに強制終了することをお勧めします。

_set -m
./some_child_script.sh &
some_pid=$!

kill -- -${some_pid}
_

まず、_set -m_コマンドはジョブ管理を有効にします(まだ有効になっていない場合)。そうでない場合、すべてのコマンド、サブシェルなどが親スクリプトと同じプロセスグループに割り当てられます(まだ有効になっていない場合)。ターミナルでコマンドを手動で実行すると)、killは「そのようなプロセスはありません」というエラーを表示します。これは、グループとして管理するバックグラウンドコマンドを実行する前に呼び出す必要があります(または、スクリプトの開始時に呼び出す必要があります)。

次に、killの引数が負であることに注意してください。これは、プロセスグループ全体を強制終了することを示しています。デフォルトでは、プロセスグループIDはグループの最初のコマンドと同じであるため、_$!_でフェッチしたPIDの前にマイナス記号を追加するだけで取得できます。より複雑なケースでプロセスグループIDを取得する必要がある場合は、_ps -o pgid= ${some_pid}_を使用してから、それにマイナス記号を追加する必要があります。

最後に、オプション_--_の明示的な終了の使用に注意してください。そうしないと、プロセスグループ引数がオプション(シグナル番号)として扱われ、killが文句を言います。十分な引数があります。これが必要になるのは、プロセスグループ引数が最初に終了する場合のみです。

これは、バックグラウンドタイムアウトプロセスの簡単な例と、可能な限りクリーンアップする方法です。

_#!/bin/bash
# Use the overkill method in case we're terminated ourselves
trap 'kill $(jobs -p | xargs)' SIGINT SIGHUP SIGTERM EXIT

# Setup a simple timeout command (an echo)
set -m
{ sleep 3600; echo "Operation took longer than an hour"; } &
timeout_pid=$!

# Run our actual operation here
do_something

# Cancel our timeout
kill -- -${timeout_pid} >/dev/null 2>&1
wait -- -${timeout_pid} >/dev/null 2>&1
printf '' 2>&1
_

これは、すべての合理的な場合に、この単純なタイムアウトのキャンセルを適切に処理する必要があります。処理できない唯一のケースは、スクリプトがすぐに終了することです(_kill -9_)。これは、クリーンアップする機会がないためです。

また、waitを追加し、その後にno-op(_printf ''_)を追加しました。これは、killコマンドによって発生する可能性のある「終了した」メッセージを抑制するためです。少しハックですが、私の経験では十分に信頼できます。

0
Haravikk