Використання jq в ланцюзі трубопроводів не дає виходу


12

Проблема 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_subThe Things Network" (саме там я також виявив проблему), - це загортати хвіст і функції 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

Це найновіше, jqвстановлене через 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

Це (в основному незадокументована) помилка jqчи з моїм розумінням трубних ланцюгів?


1
FWIW у вас тут є досить (добре, трохи) дивне налаштування, tail -fяке забезпечує постійне введення програми та teeобробку результатів. Якщо ви все ще потребували відповіді, я б запропонував спростити ланцюжок до <in.json jq '.f1' >out.jsonтого, щоб ви могли звузити те, що це викликає.
David Z

Дивіться також BashFAQ # 9 - Що таке буферизація? Або чому мій командний рядок не дає результату:tail -f logfile | grep 'foo bar' | awk ...
Чарльз Даффі,

Всі чудові поради щодо майбутніх зусиль, дякую. FWIW, tailбіт виник через зусиль, щоб розбити трубу вниз (запустіть першу команду, трійник і перенаправлення на файл, хвіст, що перейдіть до наступної команди, переадресуйте на файл тощо) і запустіть її постійно в секціях. Хоча <це є гарним інструментом, про який потрібно пам’ятати.
Хіт Рафті

Відповіді:


20

Вихід від jqбуферизований, коли його стандартний вихід є трубопровідним.

Щоб запитувати, що jqзмиває його вихідний буфер після кожного об'єкта, використовуйте його --unbufferedпараметр, наприклад

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

З jqпосібника:

--unbuffered

Промийте висновок після друку кожного об'єкта JSON (корисно, якщо ви підключаєте повільне джерело даних до іншого, jqа вихідний файл інший jq).


Далі, як я б налагоджував це, для того, щоб з'ясувати, що буферизація випуску була проблемою, припускаючи, що я не буду просто здогадуватися про те, що буде запускати частину 'jq' під 'ltrace' та / або 'strace'. Було б очевидно, що він викликає функції виводу C stdio, але не викликає syscall write (2).
AnotherSmellyGeek

1
@AbodySmellyGeek Можливо, або еквівалентна утиліта відстеження в наших Unices (зауважте, що ОП використовує Homebrew, це означає, що вони на macOS, і я на OpenBSD, жодна з яких не має цих інструментів Linux). Інша можливість полягає в тому, щоб просто знати, що буферизація на виході може статися за певних обставин :-)
Kusalananda

Блискуча. І дуже ціную всі поради щодо налагодження цього питання в майбутньому. Буферизація була однією з моїх перших сумнівів, але інша поведінка трубопроводів спонукала мої зусилля налагодження.
Хіт Рафті

6

Що ви бачите тут, це буферизація C stdio у дії. Він буде зберігати вихід у буфері до тих пір, поки не досягне певної межі (може бути 512 байт, або 4 КБ або більше), а потім надішле все це відразу.

Це буферизація автоматично відключається, якщо stdout підключений до терміналу, але коли він підключений до труби (наприклад, у вашому випадку), це дозволить цю буферну поведінку.

Звичайний спосіб відключення / керування буферизацією - це використання setvbuf()функції (див. Цю відповідь для отримання більш детальної інформації), але це потрібно зробити в самому вихідному коді jq, тому, можливо, не для вас щось практичне ...

Існує рішення ... (Злом, можна сказати.) Існує програма під назвою "unbuffer", яка поширюється з "очікувати", яка може створити псевдотермінал і підключити його до програми. Тож, хоч jqвсе ще буде записатись у трубу, він подумає, що це запис у термінал, і буферний ефект буде відключений.

Встановіть пакет "очікувати", який має поставитись з "unbuffer", якщо у вас його ще немає ... Наприклад, на Debian (або Ubuntu):

$ sudo apt-get install expect

Тоді ви можете використовувати цю команду:

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

Дивіться також цю відповідь, щоб отримати детальнішу інформацію про "скасувати", і тут можна знайти сторінку чоловіка .


Мені подобається, що ви пояснили, чому трапляється спостережувана поведінка, але, як зазначив Кусаланданда, jqспоконвічно реалізується нерозподілений вихід, тому немає необхідності в рішенні.
David Z

Ах дуже приємно! Я почав заглядати на jqсторінку чоловіка, але через деякий час набрид і пішов робити інші речі ... Добре знати, що є щось подібне! :-)
filbranden

1
Проти, GNU coreutils поставляються з допомогою stdbuf -o0яких буде вводити код через LD_PRELOAD і робити setvbuf()магічний дзвінок за вас. Чи працює він на macOS, я не впевнений.
користувач1686

1
Поки expectпопередньо встановлено на macos, unbufferні. Однак це є частиною пакету Homebrew, тобто щодо macos brew install expect.
Хіт Рафті,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.