Menggunakan jq dalam rantai pipa tidak menghasilkan output

12

Masalah jqmemerlukan filter eksplisit ketika output diarahkan kembali dibahas di seluruh web. Tapi saya tidak dapat mengarahkan output jika jqmerupakan bagian dari rantai pipa, bahkan ketika filter eksplisit sedang digunakan.

Mempertimbangkan:

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

Seperti yang diharapkan, output di terminal asli dari jqperintah adalah:

1
3

Tetapi jika saya menambahkan segala jenis pengalihan atau pemipaan ke akhir jqperintah, hasilnya menjadi sunyi:

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

Tidak ada output yang muncul di terminal pertama dan out.txt kosong.

Saya sudah mencoba ratusan variasi tetapi ini masalah yang sulit dipahami. Satu-satunya solusi yang saya temukan , seperti yang ditemukan melalui mosquitto_subdan The Things Network (yang merupakan tempat saya juga menemukan masalah), adalah untuk membungkus fungsi ekor dan jq dalam skrip shell:

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

Kemudian:

./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

Dan benar saja, output muncul:

1
3

Ini dengan yang terbaru jqdiinstal melalui Homebrew:

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

Apakah ini bug (sebagian besar tidak berdokumen) di jqatau dengan pemahaman saya tentang rantai pipa?

Heath Raftery
sumber
1
FWIW Anda memiliki pengaturan yang aneh (well, sedikit) aneh di sini, menggunakan tail -funtuk memberikan input terus menerus ke suatu program dan teeuntuk memproses output. Jika Anda masih membutuhkan jawaban, saya akan menyarankan untuk menyederhanakan rantai <in.json jq '.f1' >out.jsonsehingga Anda dapat mempersempit apa yang menyebabkannya.
David Z
Lihat juga BashFAQ # 9 - Apa itu buffering? Atau, mengapa baris perintah saya tidak menghasilkan keluaran:tail -f logfile | grep 'foo bar' | awk ...
Charles Duffy
Semua saran yang bagus untuk upaya di masa depan, terima kasih. FWIW, tailbit muncul dari upaya untuk memecah pipa (jalankan perintah pertama, tee dan redirect ke file, ekor itu, pipa ke perintah berikutnya, redirect ke file, dll) dan jalankan terus menerus dalam beberapa bagian. Ini <adalah alat yang baik untuk diingat.
Heath Raftery

Jawaban:

19

Output dari jqbuffered ketika output standarnya disalurkan.

Untuk meminta yang jqmem-flush buffer outputnya setelah setiap objek, gunakan --unbufferedopsinya, mis

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

Dari jqmanual:

--unbuffered

Siram output setelah setiap objek JSON dicetak (berguna jika Anda memipip sumber data yang lambat ke jqdan memipipkan jqoutput di tempat lain).

Kusalananda
sumber
Selanjutnya, cara saya akan men-debug ini, untuk mengetahui bahwa buffering keluaran adalah masalah, dengan asumsi saya tidak akan hanya menebak itu, akan menjalankan bagian 'jq' di bawah 'ltrace' dan / atau 'strace'. Jelas bahwa itu memanggil fungsi keluaran C stdio, tetapi tidak memanggil syscall write (2).
AnotherSmellyGeek
1
@AnotherSmellyGeek Mungkin, atau utilitas penelusuran yang setara pada Unices kami (perhatikan bahwa OP menggunakan Homebrew, yang berarti mereka menggunakan MacOS, dan saya menggunakan OpenBSD, yang keduanya tidak memiliki alat Linux ini). Kemungkinan lain adalah hanya mengetahui bahwa buffering output dapat terjadi dalam keadaan tertentu :-)
Kusalananda
Cemerlang. Dan sangat menghargai semua saran tentang debugging ini di masa depan. Buffering adalah salah satu keraguan pertama saya, tetapi perilaku berbeda untuk piping membingungkan upaya debugging saya.
Heath Raftery
6

Apa yang Anda lihat di sini adalah buffering C stdio dalam aksi. Ini akan menyimpan output pada buffer hingga mencapai batas tertentu (mungkin 512 byte, atau 4KB atau lebih besar) dan kemudian mengirimkannya sekaligus.

Buffer ini dinonaktifkan secara otomatis jika stdout terhubung ke terminal, tetapi ketika terhubung ke pipa (seperti dalam kasus Anda), buffering ini akan mengaktifkan perilaku buffering ini.

Cara biasa untuk menonaktifkan / mengontrol buffering adalah menggunakan setvbuf()fungsi (lihat jawaban ini untuk lebih jelasnya), tetapi itu perlu dilakukan dalam kode sumbernya jqsendiri, jadi mungkin bukan sesuatu yang praktis untuk Anda ...

Ada solusinya ... (Retas, bisa dikatakan.) Ada program yang disebut "unbuffer", yang didistribusikan dengan "harapkan" yang dapat membuat terminal pseudo dan menghubungkannya ke program. Jadi, meskipun jqmasih akan menulis ke pipa, itu akan berpikir itu menulis ke terminal, dan efek buffering akan dinonaktifkan.

Instal paket "expect", yang seharusnya datang dengan "unbuffer", jika Anda belum memilikinya ... Misalnya, di Debian (atau Ubuntu):

$ sudo apt-get install expect

Kemudian Anda dapat menggunakan perintah ini:

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

Lihat juga jawaban ini untuk beberapa detail lebih lanjut tentang "unbuffer", dan Anda dapat menemukan halaman manual di sini juga .

filbranden
sumber
Saya suka bahwa Anda telah menjelaskan mengapa perilaku yang diamati terjadi, tetapi seperti yang Kusalananda tunjukkan, jqsecara alami mengimplementasikan output yang tidak terganggu sehingga tidak perlu untuk menyelesaikannya.
David Z
Ah sangat bagus! Saya mulai mencari di jqhalaman manual tetapi bosan setelah beberapa saat dan pergi untuk melakukan hal-hal lain ... Senang mengetahui ada sesuatu seperti itu! :-)
filbranden
1
Protip, GNU coreutils datang dengan stdbuf -o0yang akan menyuntikkan kode melalui LD_PRELOAD dan melakukan setvbuf()panggilan ajaib untuk Anda. Apakah itu berfungsi pada macOS, saya tidak yakin.
user1686
1
Saat expectsudah diinstal pada Macos, unbuffertidak. Namun itu adalah bagian dari paket Homebrew, jadi pada macro, brew install expectakan dilakukan.
Heath Raftery