LoginSignup
26
19

More than 5 years have passed since last update.

パイプ処理における遅延評価の問題(SIGPIPE問題)

Last updated at Posted at 2016-01-18

パイプ処理における問題といえば、例のcat file | while readの同一変数が別コンテキストになる問題でしょ?と思われたかもしれないですが、それとはまた別のお話でして。

seq 1 1000 | head -10

上記のコードを実行すると、画面上には110が表示されます。

パイプ前段のプロセスが無限に標準出力する場合でも、パイプ後段のプロセスが必要な結果だけ取得できれば事足りるので、前段プロセスが愚直に最後まで計算を続けたりする必要はない。これはかしこい。

for((i=0; ;i++)){      # 終了条件が無いが無限ループにならない
    builtin echo "i=$i"
} | head -10

ただ、このようなイケてる遅延評価があだとなり、結果的に、ユーザの思惑に反する挙動をしてしまうケースもあります。

例えば以下のコードの場合、書かれてある通りに解釈すれば、本来ファイルにはi=99まで出力されてしかるべきなんですが、実行してみるとi=15までしか出力されませんでした。

i=16を書き込もうとしたタイミングで、書き込もうとしたパイプが後段プロセスによってクローズ済みだったため、SIGPIPEが発生したというところでしょうか。

rm -f ./sample.log
for((i=0; i<100; i++)){
    builtin echo "i=$i"
    builtin echo "i=$i" >>./sample.log
} | head -10
sample.log
$ cat ./sample.log
i=0
i=1
i=2
...
i=14
i=15    # i=99まで出力されるべきが、i=15の直後でSIGPIPE発生
$

試しに、以下のようなseq2コマンド(SIGPIPEを受信したタイミングでメッセージをエラー出力する)を実装し、パイプにつなげてみました。

seq2.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void catch(int signum)
{
    signal(SIGPIPE, SIG_IGN);
    fprintf(stderr, "**** signal(%d) is received !!! ****\n", signum);
    exit(1);
}
int main(int argc, char **argv)
{
    signal(SIGPIPE, catch);
    if(argc<4)
        exit(1);

    long start = atol(argv[1]);
    long end = atol(argv[2]);
    long step = atol(argv[3]);

    for(long i=start; i<=end; i+=step)
        printf("%ld\n", i);

    printf("**** end ****\n");
    return 0;
}

以下が実行結果です。確かに、SIGPIPEが発生しています。

実行結果
$ ./seq2 1 2147483647 1 | head -10
1
2
3
4
5
6
7
8
9
10
**** signal(13) is received !!! ****

これ知らないと、ハマりそうですね。パイプはあくまで「I/Oを一方向につなげて多段フィルタをかける」ためのもので、並列処理だぜヒャッホー!ってなノリで使っちゃ危険ということですね。

26
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
19