あるbashシェルスクリプトの終了時に、そのスクリプトが立てたバックグランドで動作するサブシェルをもれなく終了させる処理を考える。
例によって現代においては奥が深い症候群に該当する知識であるので、くれぐれもシンドロームには気を付けるように!
#!/usr/bin/env bash
{
sleep 100 & # back ground job 1 !
sleep 100 & # back ground job 2 !
sleep 100 & # back ground job 3 !
wait
} & # this is also back ground job 4 !
sleep 100 & # back ground job 5 !
bashは驚くことにこういうバックグラウンドジョブ1,2,3,5やサブシェル4のプロセスを終了しないまま自分自身のプロセスは終了する。さらにこれらの残ったプロセスはsourceでない限り呼び出し元のジョブコントロールには残らないためjobs
には表示されずしたがってkill $(jobs -p)
でも終了できない。
紐づけられているTTYが呼び出し元と同一のためps
では見える。呼び出し元シェルを終了させることによって終了するにはする。disown
されるとこうはならない。知らないうちに溜まっていくことは無いがスクリプト実行ごとに終了されてしかるべきである。強引に終了すると。。。
shutdown() {
ps -o pid -C sleep | tail -n +2 | xargs kill
}
trap shutdown EXIT
trapするシグナルはなんでもよいしCtrlCしないなら別にtrapでなくともスクリプトの最後でもいい。いまサブシェル4はsleepをkillすれば勝手に終了していくことはわかっているのでこれですべての子プロセスを終了させてからスクリプトを終了できる。ここまでであればpkill sleep
と同じなのでつっこみを入れたくなるだろう。
デメリットがある。psは-C無しだと同一TTYのプロセスだけを表示したが-Cでコマンドを指定するとシステム上のすべてのsleepコマンドを列挙してしまう。したがって他にsleepを打っているスクリプトなどがあるとよくない。pkill sleep
もおそらく同じ。
ちなみにtailはps hでも代用できるがman ps
曰く問題があるらしい。
No header. (or, one header per screen in the BSD personality). The h option is problematic. Standard BSD ps uses this option to print a header on each page of output, but older Linux ps uses this option to totally disable the header. This version of ps follows the Linux usage of not printing the header unless the BSD personality has been selected, in which case it prints a header on each page of output. Regardless of the current personality, you can use the long options --headers and --no-headers to enable printing headers each page or disable headers entirely, respectively.
shutdown() {
ps -o pid,cmd --tty $(tty) | tail -n +2 | while read -ra line; do
if [[ ${line[1]} == *sleep* ]]; then
kill "${line[0]}"
fi
done
}
trap shutdown EXIT
これは動作する。
悲しいかな、psで-Cと--ttyのand指定はどうやら難しい。cmdを表示させて一個一個bash君に比較して終了を打ってもらう必要がある。なぜそのへんのスクリプトがデフォルトもしくはオプション一つでやりそうな終了処理のためにループを書く必要があるのかという気分さえ我慢すれば意外と動く。各自のバックグラウンドの散らかりようによって柔軟に終了処理ができる。各々のサブシェルがwaitの後に終了処理を置いていたりする場合はツボをつくように必要なコマンドだけにシグナルを送出していく。
柔軟さを捨てるとさすがにpkillにもTTYを指定するオプションはあった。
pkill -t $(tty) sleep
これは動かない。/dev/pts/1
という指定が良くないようだ。pts/1
としてやると動くがttyコマンドの結果に処理を加えないといけないところが微妙。
TTY=$(tty)
pkill -t pts/${TTY##*/} sleep
こうなるだろうが何しているかわからなすぎる。現代においては奥が深い症候群に該当する知識であるので、くれぐれもシンドロームには気を付けるように!以上!