bashのサブシェル終了trapなど

/ bash

ある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

こうなるだろうが何しているかわからなすぎる。現代においては奥が深い症候群に該当する知識であるので、くれぐれもシンドロームには気を付けるように!以上!