Die Verwendung von jq innerhalb der Pipe-Kette erzeugt keine Ausgabe

12

Das Problem, jqdass ein expliziter Filter erforderlich ist, wenn die Ausgabe umgeleitet wird, wird im gesamten Web behandelt. Ich kann die Ausgabe jedoch nicht umleiten, wenn sie jqTeil einer Pipe-Kette ist, selbst wenn ein expliziter Filter verwendet wird.

Erwägen:

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

Wie erwartet jqlautet die Ausgabe des Befehls im ursprünglichen Terminal :

1
3

Wenn ich am Ende des jqBefehls eine Umleitung oder ein Piping hinzufüge , wird die Ausgabe stumm geschaltet:

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

Im ersten Terminal wird keine Ausgabe angezeigt und out.txt ist leer.

Ich habe Hunderte von Variationen ausprobiert, aber es ist ein schwer fassbares Problem. Die einzige Problemumgehung habe ich festgestellt , wie entdeckt durch mosquitto_subund die Dinge Network (das war , wo ich auch das Problem entdeckt), ist die Schwanz zu wickeln und jq Funktionen in einem Shell - Skript:

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

Dann:

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

Und sicher erscheint die Ausgabe:

1
3

Dies ist mit der neuesten jqVersion von Homebrew installiert:

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

Ist dies ein (größtenteils undokumentierter) Fehler in jqoder mit meinem Verständnis von Rohrketten?

Heath Raftery
quelle
1
FWIW Sie haben hier eine ziemlich (naja, etwas) seltsame Konfiguration, die verwendet wird tail -f, um einem Programm fortlaufende Eingaben zu geben und teedie Ausgabe zu verarbeiten. Wenn Sie noch eine Antwort benötigen, hätte ich vorgeschlagen, die Kette zu vereinfachen, <in.json jq '.f1' >out.jsondamit Sie eingrenzen können, was die Ursache ist.
David Z
Siehe auch BashFAQ # 9 - Was ist Pufferung? Oder warum erzeugt meine Befehlszeile keine Ausgabe:tail -f logfile | grep 'foo bar' | awk ...
Charles Duffy
Alles gute Ratschläge für zukünftige Bemühungen, danke. FWIW, das tailBit ist aus dem Versuch entstanden, die Pipe abzubrechen (den ersten Befehl ausführen, tee und in die Datei umleiten, tailen, zum nächsten Befehl umleiten, in die Datei umleiten usw.) und kontinuierlich in Abschnitten auszuführen. Dies <ist jedoch ein gutes Werkzeug, das Sie im Hinterkopf behalten sollten.
Heath Raftery

Antworten:

19

Die Ausgabe von jqwird gepuffert, wenn die Standardausgabe weitergeleitet wird.

Um anzufordern, dass jqder Ausgabepuffer nach jedem Objekt geleert wird, verwenden Sie seine --unbufferedOption, z

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

Aus dem jqHandbuch:

--unbuffered

Leeren Sie die Ausgabe, nachdem jedes JSON-Objekt gedruckt wurde (nützlich, wenn Sie eine langsame Datenquelle in die Ausgabe leiten jqund diese an eine jqandere Stelle leiten).

Kusalananda
quelle
Die Art und Weise, wie ich dies debuggen würde, um herauszufinden, dass die Ausgabepufferung das Problem war, unter der Annahme, dass ich das nicht einfach erraten würde, wäre, den 'jq'-Teil unter' ltrace 'und / oder' strace 'auszuführen. Es ist offensichtlich, dass es C stdio-Ausgabefunktionen aufruft, aber nicht den write (2) -Syscall aufruft.
AnotherSmellyGeek
1
@AnotherSmellyGeek Möglicherweise oder das äquivalente Tracing-Dienstprogramm auf unseren Unices (beachten Sie, dass das OP Homebrew verwendet, was bedeutet, dass sie unter macOS laufen, und ich bin auf OpenBSD, von dem keines diese Linux-Tools hat). Eine andere Möglichkeit besteht darin, nur zu wissen, dass unter bestimmten Umständen eine Ausgabepufferung stattfinden kann :-)
Kusalananda
Brillant. Und schätzen wirklich alle Ratschläge zum Debuggen in der Zukunft. Das Puffern war einer meiner ersten Zweifel, aber das andere Verhalten beim Piping hat meine Debugging-Bemühungen durcheinander gebracht.
Heath Raftery
6

Was Sie hier sehen, ist die C stdio-Pufferung in Aktion. Die Ausgabe wird in einem Puffer gespeichert, bis eine bestimmte Grenze erreicht ist (möglicherweise 512 Byte oder 4 KB oder mehr), und dann auf einmal gesendet.

Diese Pufferung wird automatisch deaktiviert, wenn stdout mit einem Terminal verbunden ist. Wenn es jedoch mit einer Pipe verbunden ist (wie in Ihrem Fall), wird dieses Pufferverhalten aktiviert.

Die übliche Methode zum Deaktivieren / Steuern der Pufferung ist die Verwendung von setvbuf() Funktion (siehe diese Antwort für weitere Einzelheiten). Dies müsste jedoch im Quellcode von jqselbst erfolgen, sodass es für Sie möglicherweise nicht praktikabel ist ...

Es gibt eine Problemumgehung ... (Ein Hack, könnte man sagen.) Es gibt ein Programm namens "Unbuffer", das mit "Expect" verteilt wird und ein Pseudoterminal erstellen und dieses mit einem Programm verbinden kann. Obwohl jqweiterhin in eine Pipe geschrieben wird, wird davon ausgegangen, dass in ein Terminal geschrieben wird, und der Puffereffekt wird deaktiviert.

Installieren Sie das Paket "expect", das mit "unbuffer" geliefert werden sollte, falls Sie es noch nicht haben ... Zum Beispiel unter Debian (oder Ubuntu):

$ sudo apt-get install expect

Dann können Sie diesen Befehl verwenden:

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

Weitere Informationen zu "Unbuffer" finden Sie auch in dieser Antwort. Hier finden Sie auch eine Manpage .

filbranden
quelle
Ich finde es gut, dass Sie erklärt haben, warum das beobachtete Verhalten auftritt, aber wie Kusalananda hervorhob, jqimplementiert es die ungepufferte Ausgabe nativ, sodass die Problemumgehung nicht erforderlich ist.
David Z
Ah sehr schön! Ich fing an, in der jqManpage nachzuschlagen, aber nach einer Weile langweilte ich mich und machte andere Sachen ... Gut zu wissen, dass es so etwas gibt! :-)
filbranden
1
Protip, GNU Coreutils kommen mit, stdbuf -o0die Code über LD_PRELOAD einschleusen und den setvbuf()magischen Aufruf für Sie erledigen . Ob es unter macOS funktioniert, weiß ich nicht.
user1686
1
Während expectauf macos vorinstalliert, unbufferist es nicht. Es ist jedoch Teil des Homebrew-Pakets, so dass es auf Macos ausreicht brew install expect.
Heath Raftery