パイプ処理における問題といえば、例のcat file | while read
の同一変数が別コンテキストになる問題でしょ?と思われたかもしれないですが、それとはまた別のお話でして。
seq 1 1000 | head -10
上記のコードを実行すると、画面上には1
~10
が表示されます。
パイプ前段のプロセスが無限に標準出力する場合でも、パイプ後段のプロセスが必要な結果だけ取得できれば事足りるので、前段プロセスが愚直に最後まで計算を続けたりする必要はない。これはかしこい。
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
$ cat ./sample.log
i=0
i=1
i=2
...
i=14
i=15 # i=99まで出力されるべきが、i=15の直後でSIGPIPE発生
$
試しに、以下のようなseq2
コマンド(SIGPIPE
を受信したタイミングでメッセージをエラー出力する)を実装し、パイプにつなげてみました。
# 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を一方向につなげて多段フィルタをかける」ためのもので、並列処理だぜヒャッホー!ってなノリで使っちゃ危険ということですね。