例えば 2 並列で無限ループでなにか処理させて、Ctrl+C で止められるようにする。
しかもシェルスクリプトではなく、コンソールにペタッと貼り付けて実行させたい。
ダメなケース
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
done
wait
このようなプロセスツリーになります。
bash,6979,6979
├─bash,7840,7840
│ └─sleep,7848,7840 1
└─bash,7841,7841
└─sleep,7850,7841 1
フォアグラウンドなプロセスはシェルである bash(6979) の wait なので、Ctrl+C しても子の Bash には SIGINT が飛ばないので死なない。
ダメなケース
サブシェルで囲んで wait がサブエシェルから実行されるようにします。
(
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
done
wait
)
シェルから直接起動しているのは一番上のサブシェルなのでそれがプロセスグループリーダーになります。
bash,6979,6979
└─bash,7963,7963
├─bash,7964,7963
│ └─sleep,7969,7963 1
└─bash,7965,7963
└─sleep,7971,7963 1
サブシェルの wait がフォアグラウンドなので Ctrl+C でプロセスグループ 7963 に SIGINT が送られて全滅するかと思ったんですがしませんでした。Bash が SIGINT を無視するからですね。次のようにただの ping とかなら止まります。
(
for no in {1..2}; do
ping "192.168.2.$no" &
done
wait
)
ダメなケース
バックグラウンドプロセスの PID を保存して trap で kill する。
(
pids=()
trap 'kill ${pids[@]}' EXIT
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
pids+=($!)
done
wait
)
バックグラウンドプロセスの Bash だけ死んで、その Bash から起動している何かは死なずに孤児る。上の例だと sleep は死なない。
ダメなケース
バックグラウンドプロセスはサブシェルと同じプロセスグループなので、プロセスグループを指定して全滅させる。
(
trap "kill -- -$$" EXIT
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
done
wait
)
$$ のようなシェル変数はサブシェルでは初期化されない。この例では $$ は親のシェルの PID なるため、うまくいかない。
下記のようにすればよく分かる。
echo "$$:$PPID" # 6979:6978
( echo "$$:$PPID" ) # 6979:6978 ... サブシェルではなく親の PID となている
成功なケース
$BASHPID ならサブシェルの PID が得られるのでそれを使う。
echo "$$:$PPID:$BASHPID" # 6979:6978:6979
( echo "$$:$PPID:$BASHPID" ) # 6979:6978:8219
(
trap "kill -- -$BASHPID" EXIT
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
done
wait
)
成功なケース
$$ とか $BASHPID とか使わなくても kill 0 で現在のプロセスグループにシグナルを送ることができる。
(
trap "kill 0" EXIT
for no in {1..2}; do
(
while sleep 1; do
printf "%s: my name is %d\n" "$(date +%H:%M:%S)" "$no"
done
) &
done
wait
)
これが一番簡単だと思います。