[unix] パイプ処理時のバッファリング

2017年5月15日月曜日

awk bash grep linux sed unix シェルスクリプト

xargsで出力を遅延させる

フィルタの役割をするコマンドは大抵バッファリングするので、teeとかでデバッグしていると、フィルタリングされたテキストがすぐに端末に表示されないことがある。
何かバッファリングの効果を目に見えるように再現できるいい方法はないかなと思ってやってみたけど、xargsを使って、無理やり出力を遅延させるのがわかりやすいかなぁ。

例えば、これだとコマンド実行の2秒後に"2"が表示される。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | grep 2
2
$

ところが、パイプの先にteeを挟むと、コマンドが終了する直前(10秒後)まで"2"が表示されない。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | grep 2 | tee -a test.log
2
$

これは出力先が端末かどうかで、バッファリングするかどうかを変えているからだね。
Linux Programmer's Manual STDIN(3)

grepのオプション

こんな時はgrepのオプションに"--line-buffered"というのがあるので、これを付けると1行毎にバッファ内容を出力してくれるので、パイプを経由しても端末にすぐに表示してくれる。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | grep --line-buffered 2 | tee -a test.log
2
$

sed, awkの場合

grepのオプションバッファリングしないようにするためにはsedにもオプションがある。

sed -u, --unbuffered

※ただし、BSD,Macの場合は"-u"ではなく、"-l"オプションを使う。
FreeBSD Man Pages SED(1)

awkにはオプションがない代わりにfflush関数があるので、printの直後で呼び出すのが定番みたい。

awk '{ print $1 $2; fflush() }'

な感じで。

なので、これらのオプションを使うと、以下のようになる。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | sed -u -n -e '/^2/p' | tee -a test.log
2
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | awk '/^2/ { print; fflush() }' | tee -a test.log
2
$

stdbuf

Linuxの場合は個々のコマンドのオプションではなく、stdbufコマンドでバッファサイズを変更して、コマンドを実行する手もある。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | stdbuf grep 2 | tee -a test.log
2
$