パイプチェーン内でjqを使用しても出力が生成されない


12

jq出力がリダイレクトされるときに明示的なフィルターが必要な問題は、Web全体で説明されています。しかしjq、明示的なフィルターが使用されている場合でも、パイプチェーンの一部である場合、出力をリダイレクトできません。

考慮してください:

touch in.txt
tail -f in.txt | jq '.f1'
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

予想どおり、jqコマンドからの元の端末の出力は次のとおりです。

1
3

ただし、jqコマンドの最後に何らかのリダイレクトまたはパイピングを追加すると、出力はサイレントになります。

rm in.txt
touch in.txt
tail -f in.txt | jq '.f1' | tee out.txt
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

最初の端末には出力が表示されず、out.txtは空です。

私は何百ものバリエーションを試しましたが、それはとらえどころのない問題です。The Things Network(問題も発見した場所)で発見したように、私が見つけた唯一の回避策mosquitto_subは、tail および jq関数をシェルスクリプトでラップすることです。

#!/bin/bash
tail -f $1 | while IFS='' read line; do
echo $line | jq '.f1'
done

次に:

./tail_and_jq.sh | tee out.txt
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

確かに、出力が表示されます:

1
3

これはjqHomebrew経由でインストールされた最新のものです:

$ echo $SHELL
/bin/bash
$ jq --version
jq-1.5
$ brew install jq
Warning: jq 1.5_3 is already installed and up-to-date

これはjq、パイプチェーンを理解している、または理解している(ほとんど文書化されていない)バグですか?


1
FWIWここでtail -fは、プログラムに連続入力を提供しtee、出力を処理するために使用する、かなり(まあ、少し)奇妙なセットアップがあります。それでも答えが必要な場合は、<in.json jq '.f1' >out.json原因を絞り込めるように、チェーンを単純化することをお勧めします。
デビッドZ

参照してくださいBashFAQ#9 - バッファリングとは何ですか?または、なぜ私のコマンドラインは出力を生成しません:tail -f logfile | grep 'foo bar' | awk ...
Charles Duffy

今後の努力のためのすべての素晴らしいアドバイス、ありがとう。FWIW、tailビットはパイプを分解する努力から生じました(最初のコマンドを実行し、ファイルにティーし、リダイレクトし、次のコマンドにパイプし、ファイルにリダイレクトするなど)、セクションで連続して実行します。<けれども心に留めておくための良いツールです。
ヒースラフティ

回答:


19

からのjq出力は、標準出力がパイプされるとバッファリングされます。

jqすべてのオブジェクトの後に出力バッファーをフラッシュすることを要求するには、その--unbufferedオプションを使用します。例えば

tail -f in.txt | jq --unbuffered '.f1' | tee out.txt

jqマニュアルから:

--unbuffered

各JSONオブジェクトが印刷された後、出力をフラッシュします(低速のデータソースをjqパイピングし、jqアウトプットを他の場所にパイピングする場合に便利です)。


さらに、これをデバッグする方法は、出力バッファリングが問題であることを理解するために、単純に推測しないと仮定して、「ltrace」および/または「strace」の下で「jq」部分を実行することです。C stdio出力関数を呼び出しているが、write(2)syscallを呼び出していないことは明らかです。
AnotherSmellyGeek

1
@AnotherSmellyGeekおそらく、またはUnicesの同等のトレースユーティリティ(OPはHomebrewを使用していることに注意してください。これは、macOS上にあり、OpenBSD上にあり、どちらのLinuxツールもありません)。別の可能性は、特定の状況下で出力バッファリングが発生する可能性があることを知ることです:-)
Kusalananda

鮮やかさ。そして、今後これをデバッグする際のアドバイスを本当に感謝します。バッファリングは私の最初の疑問の1つでしたが、パイピングのさまざまな動作がデバッグ作業を混乱させていました。
ヒースラフティ

6

ここで見ているのは、C stdioのバッファリングの動作です。特定の制限(512バイト、または4KB以上)に達するまで出力をバッファに保存し、それを一度に送信します。

stdoutが端末に接続されている場合、このバッファリングは自動的に無効になりますが、パイプに接続されると(この場合など)、このバッファリング動作が有効になります。

バッファリングを無効化/制御する通常の方法は、setvbuf()関数を使用することです(詳細についてはこの回答を参照してください)が、それはjqそれ自体のソースコードで行う必要があるため、実用的ではないかもしれません...

回避策があります...(ハック、言うかもしれません。)「unbuffer」と呼ばれるプログラムがあり、「expect」とともに配布され、擬似端末を作成してプログラムに接続できます。そのjqため、パイプへの書き込みは引き続き行われますが、端末への書き込みであると見なされ、バッファリング効果は無効になります。

「expect」パッケージをインストールします。これは、「unbuffer」が付属しているはずです(まだ持っていない場合)。たとえば、Debian(またはUbuntu)の場合:

$ sudo apt-get install expect

次に、このコマンドを使用できます。

$ tail -f in.txt | unbuffer -p jq '.f1' | tee out.txt

「アンバッファ」の詳細については、この回答も参照してください。マニュアルページもここにあります


観測された動作が発生する理由を説明したのは気に入っていますが、Kusalanandaが指摘したように、jqバッファされていない出力をネイティブに実装しているため、回避策は必要ありません。
デビッドZ

ああ、いいね!私はjqマニュアルページを見始めましたが、しばらくして退屈し、他のことをしに行きました...そのような何かがあることを知ってうれしいです!:-)
フィルブランデン

1
Protip、GNU coreutilsが付属しstdbuf -o0ており、LD_PRELOADを介してコードを挿入し、setvbuf()魔法の呼び出しを行います。macOSで動作するかどうかはわかりません。
user1686

1
一方でexpectMacOSでプリインストールされている、unbufferではありません。ただし、Homebrewパッケージの一部であるため、macosでも同様brew install expectです。
ヒースラフティ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.