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