Использование 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 (именно там я и обнаружил проблему), - это обернуть функции tail и 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вы могли сузить круг причин ее возникновения.
Дэвид З
Смотрите также BashFAQ # 9 - Что такое буферизация? Или, почему моя командная строка не производит вывод:tail -f logfile | grep 'foo bar' | awk ...
Чарльз Даффи
Всем отличный совет для будущих усилий, спасибо. FWIW, tailбит возник из-за попыток разорвать канал (запустить первую команду, выполнить и перенаправить в файл, выполнить это, передать в следующую команду, перенаправить в файл и т. Д.) И запустить его непрерывно в разделах. Это <хороший инструмент, чтобы иметь в виду, хотя.
Хит Рэфтери

Ответы:

19

Вывод из jqбуферизируется, когда его стандартный вывод передается по конвейеру.

Чтобы запросить jqочистку выходного буфера после каждого объекта, используйте его --unbufferedопцию, например

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

Из jqруководства:

--unbuffered

Сбрасывайте вывод после того, как каждый объект JSON напечатан (полезно, если вы подключаете медленный источник данных jqи выводите jqвывод в другом месте).

Кусалананда
источник
Далее, способ, которым я отлаживал бы это, чтобы выяснить, что выходная буферизация была проблемой, предполагая, что я не буду просто догадываться об этом, состоял бы в том, чтобы запустить часть 'jq' в 'ltrace' и / или 'strace'. Было бы очевидно, что он вызывает функции вывода C stdio, но не вызывает системный вызов write (2).
AnotherSmellyGeek
1
@AnotherSmellyGeek Возможно, или эквивалентная утилита трассировки в наших Unices (обратите внимание, что OP использует Homebrew, что означает, что они на macOS, а я на OpenBSD, ни на одном из которых нет этих инструментов Linux). Другая возможность - просто знать, что буферизация вывода может происходить при определенных обстоятельствах :-)
Кусалананда
Brilliant. И очень ценю все советы по отладке этого в будущем. Буферизация была одним из моих первых сомнений, но другое поведение при работе с трубами сбивало с толку мои усилия по отладке.
Хит Рэфтери
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

Смотрите также этот ответ для более подробной информации о "unbuffer", и вы также можете найти страницу справочника здесь .

filbranden
источник
Мне нравится, что вы объяснили, почему наблюдается наблюдаемое поведение, но, как указал Кусалананда, jqнативно реализует небуферизованный вывод, поэтому нет необходимости в обходном пути.
Дэвид З
Ах, очень мило! Я начал искать в jqсправочной странице, но через некоторое время мне стало скучно, и я занялся другими делами ... Приятно знать, что что-то подобное происходит! :-)
filbranden
1
Protip, GNU coreutils поставляется с ним, stdbuf -o0который внедрит код через LD_PRELOAD и сделает setvbuf()за вас волшебный вызов. Работает ли это на macOS, я не уверен.
user1686
1
Пока expectпредустановлен на macos, unbufferнет. Тем не менее, это часть пакета Homebrew, так что на macos, brew install expectподойдет.
Хит Рэфтери