在管道链中使用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为空。

我已经尝试了数百种变体,但这是一个难以捉摸的问题。通过The Things Network(也是我发现问题的地方)发现的,我发现的唯一解决方法mosquitto_sub是将tail jq函数包装在shell脚本中:

#!/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 ...
Charles Duffy

所有关于未来工作的很棒的建议,谢谢。FWIW,主要是tail通过分解管道(运行第一个命令,发球并重定向到文件,拖尾,管道传输到下一个命令,重定向到文件等)并在各节中连续运行来完成的。不过,这<是一个不错的工具。
Heath Raftery

Answers:


20

jq当标准输出通过管道传输时,来自的输出将被缓冲。

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

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

jq手册中:

--unbuffered

在打印每个JSON对象之后刷新输出(如果您将缓慢的数据源传递到其他地方jq并传递输出,则很有用jq)。


此外,假设不是简单地猜测,我将以此调试输出缓冲以找出输出缓冲的问题,那就是在“ ltrace”和/或“ strace”下运行“ jq”部分。很明显,它正在调用C stdio输出函数,而不是在调用write(2)系统调用。
AnotherSmellyGeek

1
@AnotherSmellyGeek可能是Unices上的等效跟踪实用程序(请注意,OP使用的是Homebrew,这意味着它们在macOS上,而我在OpenBSD上,这两个都没有这些Linux工具)。另一种可能性是仅仅知道在某些情况下可能发生输出缓冲:-)
库萨兰达

辉煌。并非常感谢将来调试时提供的所有建议。缓冲是我的第一个怀疑,但是管道的不同行为使我的调试工作陷入混乱。
Heath Raftery

6

您在这里看到的是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”的更多详细信息,另请参见此答案,您也可以在此处找到手册页


我喜欢您已经解释了为什么观察到的行为会发生的原因,但是正如Kusalananda指出的那样,它jq本机实现了非缓冲输出,因此不需要解决方法。
David Z

很好啊!我开始在jq手册页中查找,但一段时间后感到无聊,然后去做其他事情……很高兴知道有这样的事情!:-)
filbranden

1
Protip,GNU coreutils随带,stdbuf -o0它将通过LD_PRELOAD注入代码并setvbuf()为您做魔术。我不确定它是否可以在macOS上运行。
user1686 '18

1
虽然expect预先安装在macOS上,但unbuffer不是。但是,它是Homebrew软件包的一部分,因此在macOS上brew install expect也可以。
Heath Raftery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.