例えば 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
)
これが一番簡単だと思います。