たいした話ではないですが、何度も忘れて書けなくなるのでメモ。
ある入力に対して複数の処理をしたいとき、入力がただのファイルだったら、まあcatを2回やりゃあいいんですが、プログラムの出力だったり、ファイルが圧縮されててbzcatしなきゃいけなかったりだとちょいと面倒です。入力の量がたいしたことなければいったんメモリに書いてあげればよくて、多くて大変だったら一時ファイルに書いてあげれば(その後の試行錯誤も見越すと)いいかと。
とはいうものの、ワンライナーでさくっとやってしまいたいときに、ゴミファイルが散乱するのも気持ち悪いので、pipe(2)してdup2(2)して楽をしたいですよね。というかほんとに楽できてるのかな? と思ったので、軽く調べてみました。
解答例
- いちばんナイーブな方法 (naive)
- seq がものすごい時間かかる処理の場合、倍の時間がかかってしまう。
seq 10000000 | grep 123 | wc -l seq 10000000 | grep 456 | wc -l
- いったん変数に代入 (memory)
- $aho がすごいメモリ食う
aho=$(seq 10000000) echo "$aho" | grep 123 | wc -l echo "$aho" | grep 456 | wc -l
- 名前付きパイプ (pipe)
- きっとこれが一番賢いと思う。けどteeがいまいちな気もする。
seq 10000000 | tee >/dev/null >(grep 123 | wc -l) >(grep 456 | wc -l)
実行結果の違い
% while :; do ps -e -ouser,comm,pid,start,etime,rss | grep nobody; sleep 1; done | tee aho
みたいなかんじでかなり適当な統計ですが、こんな感じになりました。
naiveがpipeのちょうど2倍くらい時間かかって、メモリはnaiveが3M, pipeが6M程度。seq 1千万だとmemoryは1GB近くメモり食う上に一番遅い、ってとこでした。
まとめ
seqがbzcatみたいに遅くて、さらに一時ファイルに書くI/Oももったいないくらいなときは、named pipeが良さそうですね。
ちなみにset -xしてあげるとこんな感じになります。
ichii386% time bash -c 'set -x; seq 10000000 | tee >/dev/null >(grep 123 | wc -l) >(grep 456 | wc -l)' + seq 10000000 ++ grep 123 ++ grep 456 + tee /dev/fd/63 /dev/fd/62 ++ wc -l ++ wc -l 49970 49970 bash -c 7.52s user 0.35s system 99% cpu 7.875 total
やっぱりteeが気持ち悪いす。もっといい方法ご存知の方はおしえてください...。