L'uso di jq all'interno della catena di tubi non produce alcun output


12

La questione della jqnecessità di un filtro esplicito quando viene reindirizzato l'output è discussa in tutto il Web. Ma non riesco a reindirizzare l'output se jqfa parte di una catena di pipe, anche quando è in uso un filtro esplicito.

Ritenere:

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

Come previsto, l'output nel jqcomando originale dal comando è:

1
3

Ma se aggiungo qualsiasi tipo di reindirizzamento o piping alla fine del jqcomando, l'output diventa silenzioso:

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

Nessun output appare nel primo terminale e out.txt è vuoto.

Ho provato centinaia di varianti ma è un problema sfuggente. L'unica soluzione che ho trovato , come scoperto attraverso mosquitto_sube The Things Network (che era anche il punto in cui ho scoperto il problema), è di avvolgere le funzioni tail e jq in uno script shell:

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

Poi:

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

E abbastanza sicuro, l'output appare:

1
3

Questo è con l'ultimo jqinstallato tramite 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

Si tratta di un bug (ampiamente non documentato) all'interno jqo con la mia comprensione delle catene di tubi?


1
FWIW qui hai un'impostazione abbastanza (bene, leggermente) strana, usando tail -fper fornire input continuo a un programma ed teeelaborare l'output. Se avessi ancora bisogno di una risposta, avrei suggerito di semplificare la catena in <in.json jq '.f1' >out.jsonmodo da poter restringere la causa.
David Z,

Vedi anche BashFAQ # 9 - Che cos'è il buffering? Oppure, perché la mia riga di comando non produce alcun output:tail -f logfile | grep 'foo bar' | awk ...
Charles Duffy,

Tutti ottimi consigli per gli sforzi futuri, grazie. FWIW, il tailbit è nato dagli sforzi per rompere il pipe (esegui il primo comando, tee e reindirizza al file, coda quello, pipe al comando successivo, reindirizza al file, ecc.) Ed eseguilo continuamente in sezioni. È comunque <un buon strumento da tenere a mente.
Heath Raftery,

Risposte:


19

L'output da jqviene bufferizzato quando viene convogliata l'output standard.

Per richiedere che jqsvuota il buffer di output dopo ogni oggetto, utilizzare la sua --unbufferedopzione, ad es

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

Dal jqmanuale:

--unbuffered

Svuota l'output dopo aver stampato ogni oggetto JSON (utile se stai eseguendo il piping di un'origine dati lenta e l'output del jqpiping jqaltrove).


Inoltre, il modo in cui eseguirò il debug di questo, al fine di capire che il problema era il buffering dell'output, supponendo che non lo avrei semplicemente indovinato, sarebbe stato quello di eseguire la parte 'jq' sotto 'ltrace' e / o 'strace'. Sarebbe ovvio che sta chiamando le funzioni di output C stdio, ma non la chiamata syscall write (2).
AnotherSmellyGeek

1
@AnotherSmellyGeek Forse, o l'utilità di traccia equivalente sui nostri Unices (nota che l'OP sta usando Homebrew, il che significa che sono su macOS, e io sono su OpenBSD, nessuno dei quali ha questi strumenti Linux). Un'altra possibilità è solo sapere che il buffering dell'output può verificarsi in determinate circostanze :-)
Kusalananda

Brillante. E apprezzo molto tutti i consigli per il debug di questo in futuro. Il buffering è stato uno dei miei primi dubbi, ma il diverso comportamento per il piping stava facendo esplodere i miei sforzi di debug.
Heath Raftery,

6

Quello che vedi qui è il buffering C stdio in azione. Memorizzerà l'output su un buffer fino a raggiungere un certo limite (potrebbe essere 512 byte o 4KB o superiore) e quindi invierà tutto in una volta.

Questo buffering viene disabilitato automaticamente se stdout è collegato a un terminale, ma quando è collegato a una pipe (come nel tuo caso), abiliterà questo comportamento di buffering.

Il solito modo per disabilitare / controllare il buffering è usare la setvbuf()funzione (vedi questa risposta per maggiori dettagli), ma ciò dovrebbe essere fatto nel codice sorgente di jqse stesso, quindi forse non è qualcosa di pratico per te ...

C'è una soluzione alternativa ... (Un hack, si potrebbe dire.) C'è un programma chiamato "unbuffer", distribuito con "prevedono" che può creare uno pseudo-terminale e collegarlo a un programma. Quindi, anche se jqcontinuerà a scrivere su una pipe, penserà che sta scrivendo su un terminale e l'effetto buffering sarà disabilitato.

Installa il pacchetto "prevede", che dovrebbe venire con "unbuffer", se non lo hai già ... Ad esempio, su Debian (o Ubuntu):

$ sudo apt-get install expect

Quindi puoi usare questo comando:

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

Vedi anche questa risposta per ulteriori dettagli su "unbuffer", e puoi trovare anche una pagina man qui .


Mi piace il fatto che tu abbia spiegato perché si verifica il comportamento osservato, ma, come ha sottolineato Kusalananda, jqimplementa in modo nativo l'output senza buffer in modo che non sia necessaria una soluzione alternativa.
David Z,

Ah molto carino! Ho iniziato a cercare nella jqpagina man ma dopo un po 'mi sono annoiato e sono andato a fare altre cose ... Buono a sapersi che c'è qualcosa del genere! :-)
filbranden

1
Protip, i coreutils GNU vengono con i stdbuf -o0quali inietterà il codice tramite LD_PRELOAD e farà la setvbuf()chiamata magica per te. Se funziona su macOS, non ne sono sicuro.
user1686

1
Mentre expectè preinstallato su macos, unbuffernon lo è. Tuttavia fa parte del pacchetto Homebrew, quindi su macos, brew install expectlo farà.
Heath Raftery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.