引数で渡した複数のストリームを行単位でマージしリアルタイム出力するコマンド作った

ツール書きました。

github.com

例えば以下のような3つのコマンドを同時に実行して、結果をリアルタイムでマージして出力したいとします。

1 (sleep 1;echo SlowSlow)
2 (sleep 0;for i in $(seq 1 25);do printf aaa;echo;done) 
3 (sleep 0;for i in $(seq 1 25);do printf bbb;echo;done)

SlowSlow の行は1秒待ってから出力なので、実際に受け取るタイミングを考えれば以下のような結果を得たいことになります。

aaa
bbb
bbb
aaa
...
...
bbb
aaa
SlowSlow

simple bash

bashでもできそうなのでやってみると、

((sleep 1;echo SlowSlow) & \
 (sleep 0;for i in $(seq 1 25);do printf aaa;echo;done) & \
 (sleep 0;for i in $(seq 1 25);do printf bbb;echo;done))

ab\n 改行が混ざってしまいます。

aaa
baaa
aaabaaa
\n
aaabaaa
\n
baaaaaa
\n
baaa
...
...
aaaSlowSlow

cat

cat を使えば行はキレイに出力されますが、引数に渡されたストリームを前から順番に処理するので、それぞれ前のストリーム待ちでブロックしてしまいます。

cat <(sleep 1;echo SlowSlow) \
 <(sleep 0;for i in $(seq 1 25);do printf aaa;echo;done) \
 <(sleep 0;for i in $(seq 1 25);do printf bbb;echo;done)  
SlowSlow  # 1秒待ってから出力
aaa            # ブロックされる。1つ目の出力SlowSlowを待って出力
aaa
...
...
bbb          # ブロックされる。2つ目のaaaが全て出力されるのを待って出力
bbb
...

paste

pasteというツールもあります。

paste -d \\n <(sleep 1;echo SlowSlow) \
 <(sleep 0;for i in $(seq 1 25);do printf aaa;echo;done) \
 <(sleep 0;for i in $(seq 1 25);do printf bbb;echo;done)  

3つの入力から1行受け取るのを待って一斉に出力します。つまりは、

SlowSlow\naaa\nbbb\n -> 出力 aaa\nbbb\n -> 出力 aaa\nbbb\n -> 出力

実際に打つと以下のようになります。

SlowSlow # 1秒待ってから出力
aaa           
bbb
aaa
bbb
...
...
aaa
bbb

linecmb

今回作ったlinecmbでは期待した結果が得られるはずです。

linecmb <(sleep 1;echo SlowSlow) \
 <(sleep 0;for i in $(seq 1 25);do printf aaa;echo;done) \
 <(sleep 0;for i in $(seq 1 25);do printf bbb;echo;done)  

実はこのツール参考にしたQiitaの記事にも書かれているfdlinecombineでも同じことができます。

github.com

しかもfdlinecombineの方が早い。頑張ってスピード上げたのですが追いつきませんでした。

ReadStringやScannerを使って書き始めたのですが、パフォーマンスが思うように出ずにだんだん低いレイヤのAPIを使っていって、systemcallまで多用して書いてみたのですが、今度はLinuxMacの互換が取れなくなり、少しレイヤあげてReadとWriteで落ち着いています。それでも、特にMacのファイルディスクリプタ周りはの動きは初めて触ったし、情報も少なくて厳しかったです。途中でそもそもそれならCで書けばいいじゃんとなったり。。。他にもまだまだ改良の余地はあるでしょうが、それは追々。

便利な利用方法

早速使って便利だと思ったのはスケールアウトで複数ならんでるWEBサーバの情報を取得したりするコマンド。

linecmb <(ssh server1 -C "tail -f /path/to/file") <(ssh server2 -C "tail -f /path/to/file") <(ssh server3 -C "tail -f /path/to/file")

ちなみにログ見るときには更にこれ組み合わせています。

Bashのパイプから受け取ったテキストで複数の単語をハイライト表示させる - YOMON8.NET

参考記事

bashでストリームデータ処理 - Qiita