O uso de jq na cadeia de encanamento não produz saída

8

O problema de jq precisar de um filtro explícito quando a saída é redirecionada é discutido em toda a web. Mas não consigo redirecionar a saída se jq fizer parte de uma cadeia de pipe, mesmo quando um filtro explícito estiver em uso.

Considere:

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

Como esperado, a saída no terminal original do comando jq é:

1
3

Mas se eu adicionar qualquer tipo de redirecionamento ou canalização ao final do comando jq , a saída ficará silenciosa:

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

Nenhuma saída aparece no primeiro terminal e o out.txt está vazio.

Eu tentei centenas de variações, mas é um problema indescritível. A única solução alternativa que encontrei , como descoberto através de mosquitto_sub e The Things Network (que foi onde também descobri o problema), é envolver as funções tail e jq em um shell script:

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

Então:

./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 com certeza, a saída aparece:

1
3

Isto é com o mais recente jq instalado via 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

Este é um bug (em grande parte não documentado) em jq ou com meu entendimento de cadeias de tubos?

    
por Heath Raftery 04.04.2018 / 05:42

2 respostas

14

A saída de jq é armazenada em buffer quando sua saída padrão é canalizada.

Para solicitar que jq limpe seu buffer de saída após cada objeto, use sua opção --unbuffered , por exemplo,

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

Do manual jq :

--unbuffered

Flush the output after each JSON object is printed (useful if you're piping a slow data source into jq and piping jq's output elsewhere).

    
por 04.04.2018 / 07:31
6

O que você está vendo aqui é o buffer de armazenamento em ação. Ele armazenará a saída em um buffer até atingir um determinado limite (pode ser de 512 bytes ou 4KB ou maior) e, em seguida, enviar tudo de uma vez.

Este buffer é desativado automaticamente se o stdout estiver conectado a um terminal, mas quando estiver conectado a um pipe (como no seu caso), ele ativará esse comportamento de armazenamento em buffer.

A maneira usual de desabilitar / controlar o buffer é usar a função setvbuf() (veja esta resposta para mais detalhes), mas isso precisaria ser feito no código fonte de jq , então talvez não seja algo prático para você ...

Há uma solução alternativa ... (Um hack, pode-se dizer.) Existe um programa chamado "unbuffer", que é distribuído com "expect" que pode criar um pseudo-terminal e conectá-lo a um programa. Portanto, mesmo que jq ainda esteja escrevendo para um pipe, ele pensará que está escrevendo em um terminal e o efeito de buffer será desativado.

Instale o pacote "expect", que deve vir com "unbuffer", se você ainda não o tiver ... Por exemplo, no Debian (ou Ubuntu):

$ sudo apt-get install expect

Então você pode usar este comando:

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

Veja também esta resposta para mais detalhes sobre "unbuffer", e você pode encontrar uma página man aqui também .

    
por 04.04.2018 / 06:47