止めるまで動きつづける統計値を延々と出力するようなプログラムの出力を加工するスクリプトというのがあるとする。
たとえばeth0のトラフィックをkB/sを表示するもの:
[sar.sh] #!/bin/sh /usr/bin/sar -n DEV 1 | awk '$2=="eth0"{print $6,$5}'
で、これをつかってあるプログラムを実行したときのトラフィックを記録したいとする。
[test.sh] #!/bin/sh ./sar.sh >sar.out & pid_sar=$! long_program kill $pid_sar cat sar.out
プログラムの実行が終わったらkillしているのだが、シェルが終了するだけでsarやawkは動いたままになる。
シェルは死ぬときにバックグラウンドのプロセスをkillしてはくれないのだ。
コマンドラインから ./sar.sh を直接実行した場合はシェル(/bin/sh ./sar.sh)だけでなくsarもawkも終了するのは、Ctrl-Cを押したときに端末ドライバ(tty)がフォアグラウンドプロセスグループに対してSIGINTを送ってくれるためだ。UNIXは今となっては無駄に端末まわりの機能が豊富なのだ。プロセスグループをつくってくれるのはコマンドラインを解釈しているログインシェルである。
スクリプト1の中からスクリプト2を実行した場合にスクリプト2がきれいに終了するには、そういうふうにシェルスクリプトを書けばよいとおもうところだが、pipeでつないでいるので個別のプログラムのpidがわからないという問題が。
ということで、一番簡単なプロセスグループをつくってしまえばokという解決方法をとることにした。
こんなかんじで自分をプロセスグループリーダにしてプロセスグループをつくる。
[setpgid.c] #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> int main(int ac, char **av) { char *prog = av[0]; if (ac < 2) return 1; if (setpgid(0, 0) < 0) { fprintf(stderr, "%s:setpgid:%s\n", prog, strerror(errno)); return 1; } execvp(av[1], av+1); fprintf(stderr, "%s:exec:%s\n", prog, strerror(errno)); return 1; }
つかいかたはこんかかんじ。コマンドを起動するときにsetpgidをはさむのと、killするときにプロセスグループにシグナルを送るためにpidにマイナスをつける。
[test.sh] #!/bin/sh ./setpgid ./sar.sh >sar.out & pid_sar=$! long_program kill -TERM -$pid_sar cat sar.out
でもいちいち呼出側にsetpgidを入れてまわるのは頭わるいのでshebangに入れる。
[sar.sh] #!./setpgid /bin/sh /usr/bin/sar -n DEV 1 | awk '$2=="eth0"{print $6,$5}'
[test.sh] #!/bin/sh ./sar.sh >sar.out & pid_sar=$! long_program kill -TERM -$pid_sar cat sar.out
プロセスグループにシグナルを送るのは自分でやるならシグナルをつかまえて自分のプロセスグループにシグナルを送りなおせばいいはず。
[sar.sh] #!./setpgid /bin/sh die() { echo "bye" trap - TERM INT kill -TERM -$$ } trap die TERM INT /usr/bin/sar -n DEV 1 | awk '$2=="eth0"{print $6,$5}'
とおもってやってみるとシェルがシグナルをうけとっても、シェルレベルのシグナルハンドラを実行してくれない。どうもシェル依存のようだ。バックグランドで実行してwaitで待てばシグナルハンドラを実行してくれるようだが、POSIX規格で決まっているわけではないので絶対安全というわけではないらしい。
#!./setpgid /bin/sh die() { echo "bye" trap - TERM INT kill -TERM -$$ } trap die TERM INT /usr/bin/sar -n DEV 1 | awk '$2=="eth0"{print $6,$5}' & wait
とりあえずの妥協点としてはこんなところか。