止めるまで動きつづける統計値を延々と出力するようなプログラムの出力を加工するスクリプトというのがあるとする。

たとえば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

とりあえずの妥協点としてはこんなところか。

http://togetter.com/li/942376

koie