在管道链中使用 jq 不会产生输出

在管道链中使用 jq 不会产生输出

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_sub物联网(这也是我发现问题的地方)发现的那样,是将尾部包裹起来shell 脚本中的 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

jq当其标准输出不是终端时,其输出将被缓冲。

要请求jq在每个对象之后刷新其输出缓冲区,请使用其--unbuffered选项,例如

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

jq手册:

--unbuffered

打印每个 JSON 对象后刷新输出(如果您将慢速数据源通过管道传输到其他地方jq并通过管道传输jq输出,则非常有用)。

答案2

您在这里看到的是正在运行的 C stdio 缓冲。它将输出存储在缓冲区中,直到达到一定限制(可能是 512 字节、4KB 或更大),然后一次性发送所有输出。

如果 stdout 连接到终端,则此缓冲会自动禁用,但当它连接到管道时(例如您的情况),它将启用此缓冲行为。

禁用/控制缓冲的常用方法是使用该setvbuf()函数(请参阅这个答案了解更多细节),但这需要在jq其本身的源代码中完成,所以可能对你来说不实用......

有一种解决方法......(有人可能会说,一种黑客攻击。)有一个名为“unbuffer”的程序,它与“expect”一起分发,可以创建一个伪终端并将其连接到程序。因此,即使jq仍然写入管道,它也会认为它正在写入终端,并且缓冲效果将被禁用。

安装“expect”包,如果你还没有“unbuffer”,它应该带有它......例如,在 Debian(或 Ubuntu)上:

$ sudo apt-get install expect

然后你可以使用这个命令:

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

也可以看看这个答案有关“unbuffer”的更多详细信息,您可以找到手册页也在这里

相关内容