web-dev-qa-db-ja.com

パイプチェーン内でjqを使用すると、出力が生成されません

出力がリダイレクトされるときにjqが明示的なフィルターを必要とする問題は、ウェブ全体で議論されています。しかし、明示的なフィルターが使用されている場合でも、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は空です。

私は何百ものバリエーションを試しましたが、それはとらえどころのない問題です。唯一の 私が見つけた回避策 は、mosquitto_subおよびThe Things Network(これも私が問題を発見した場所)を通じて発見されたように、尾を折り返すandシェルスクリプトの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

これは、Homebrewを介してインストールされた最新のjqを使用しています。

$ 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の(ほとんど文書化されていない)バグですか、それともパイプチェーンについての私の理解によるものですか?

12
Heath Raftery

jqからの出力は、標準出力がパイプされるときにバッファーに入れられます。

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

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

jqマニュアルから:

--unbuffered

各JSONオブジェクトが出力された後、出力をフラッシュします(遅いデータソースをjqにパイプし、jqの出力を別の場所にパイプする場合に便利です)。

20
Kusalananda

ここに表示されているのは、動作中のC stdioバッファリングです。一定の制限(512バイト、または4KB以上の可能性があります)に達するまで出力をバッファーに格納し、それを一度に送信します。

Stdoutがターミナルに接続されている場合、このバッファリングは自動的に無効になりますが、(ケースのように)パイプに接続されている場合、このバッファリング動作が有効になります。

バッファリングを無効化/制御する通常の方法は、setvbuf()関数を使用することです(詳細については この回答 を参照)。ただし、これはjq自体なので、おそらく実用的ではありません...

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

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

$ Sudo apt-get install expect

その後、次のコマンドを使用できます。

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

「unbuffer」の詳細については this answer も参照してください。 manページもここにあります を見つけることができます。

6
filbranden