如何在 awk 中调用 jq?

如何在 awk 中调用 jq?

基本上我有一个 file.log 如下

blah blah
blah blah
Hello world | {"foo": "bar"}
blah blah
Hello earth | {"foo1": "bar1"}

现在我的目标是编写一些 shell 命令以获得如下所需的输出:

Hello earth | "bar"
Hello earth | "bar1"

目前,这就是我所拥有的:

grep Hello file.log | awk -F "|" '{print $1, system("jq " $2)}'

但是调用 jq 给了我这个错误:

jq: error: syntax error, unexpected ':', expecting $end (Unix shell quoting issues?) at <top-level>, line 1:
bin:application   
jq: 1 compile error

我想这是因为在 system() 内部,我的 $12 被去掉了所有引号字符 ("),因此 JQ 无法识别它的 json。有什么建议吗?

答案1

你这里有几个问题

  • system不返回要打印的内容,它返回您执行的命令的退出值(如果一切正常则返回 0)。您将看到 JSON 解码数据,然后是一行Hello earth 0
  • JSON 字符串中的双引号会被 shell 吞掉。您正在执行的结果命令是jq {foo: bar}(两个参数,JSON 不再引用)
  • 如果$2包含特殊字符$,例如 ,您的 shell 将解释它们
  • 即使使用正确的引用,也jq不会像这样调用,它需要一个过滤器作为第一个参数(例如“ .”),并且它希望从文件或标准输入读取 JSON 输入
  • 从日志构建命令并执行它具有巨大的安全含义(如果$2是的话怎么办; rm -rf ~?)。如果可以的话最好避免它。

抛开安全问题不谈,下面是一个awk在大多数情况下都可以工作的代码:

awk -F "|" '{ printf "%s", $1; system("echo \x27" $2 "\x27 | jq .")}'

它的作用是通过$2单引号 ( \x27) 发送到jqstdin。

但问题依然存在

  • 如果$2包含单引号,它将破坏整个命令
  • 如果$2以破折号开头(不太可能),它将被解释为选项echo(我们可以使用printf命令而不是echo
  • 已经提到的安全问题(例如,如果$2包含...'; rm -r ~; : ' ...在字符串中的任何位置)

现在有更好的awk代码

awk -F "|" '{ printf "%s", $1; print $2 | "jq ."; close("jq ."); }'

由于是通过 stdin$2发送到jq进程,但现在使用awk管道,因此 shell 不再解释它,解决了上述所有问题。该jq命令必须在每一行关闭(终止),因此调用close().

答案2

另一种解决方案,不使用 awk,仅使用杰克

诀窍是使用--原始输入,这会将文件读取为 string 数组。

因此,对于每一行,测试符号是否|这里将字符串切入 ,并将该部分解析为 json 字符串

jq -j --raw-input  '
    . as $line | 
    if index("|") >= 0  
    then  
      [ .[:index("|")-1] ,.[index("|")+2:] ]  
    else 
      empty
    end | 
   [ .[0] , ( .[1] | fromjson | to_entries | .[0].value ) ] |
   .[0] , " | \"" ,.[1] , "\"\n" '  /tmp/file.log

答案3

xhienne 提供了很好的概述现有代码的问题,以及您想要完成的任务的一个很好的替代方案。

以下是另一种选择:根本不要尝试调用jqawk而是让awk脚本创建正确的 JSON 输出。

$ awk -F '|' 'BEGIN { print "[" } $2 != "" { if (t != "") print t ","; t = $2 } END { print t, "]" }' file | jq .
[
  {
    "foo": "bar"
  },
  {
    "foo1": "bar1"
  }
]

awk代码本身将从找到的 JSON 对象生成以下 JSON 数组(给出问题中的示例):

[
 {"foo": "bar"},
 {"foo1": "bar1"} ]

这使您可以更自由地工作,jq而不会使您的脚本太难以维护和理解。

在脚本中使用变量t只是确保我们不会在最后一个 JSON 对象后面出现尾随逗号。

答案4

长话短说:

jq -r -R '
  select(contains(" | ")) |
  split(" | ") |
  .[0] as $text |
  (.[1] | fromjson | to_entries | .[0].value ) as $json_obj_value |
  "\($text) | \($json_obj_value)"
' yourlogfile.log

完整答案

大多数人并没有意识到它有多么强大jq(尽管也可以这么说awk)。

作为库萨罗南达在他们的回答中深思熟虑地指出,你最好的朋友是旗帜-R,它将逐行读取输入作为 json 字符串而不是 json 对象。这样我们就可以自由地只处理内部的字符串jq,根本不需要awk

这是文档如何描述它的版本1.6

--raw-input/-R:

不要将输入解析为 JSON。相反,每行文本都会作为字符串传递到过滤器。如果与 结合使用--slurp,则整个输入将作为单个长字符串传递到过滤器。

对于您想要的输出,您还需要旗帜-r,这使得它在终端中打印裸字符串而不是 json 字符串。

再次来自文档

--raw-output/ -r

使用此选项,如果过滤器的结果是字符串,那么它将直接写入标准输出,而不是格式化为带引号的 JSON 字符串。这对于使 jq 过滤器与非基于 JSON 的系统对话非常有用。

因此,解决这个问题后,有几种方法可以解决这个问题jq

作为EchoMike444 已经用更命令式的方式回答了,我尝试使用一种不同的方法,这种方法更加流水线化。

jq -r -R '
  select(contains(" | ")) |
  split(" | ") |
  .[0] as $text |
  (.[1] | fromjson | to_entries | .[0].value ) as $json_obj_value |
  "\($text) | \($json_obj_value)"
' yourlogfile.log

基本上我们

  1. 丢弃任何不含“|”的行
  2. 将每一行分成两部分
  3. 将左侧部分装订起来$text以便于阅读
  4. 将正确的部分解析为 json,获取它的第一个值并将其放入$json_obj_value绑定中以方便阅读
  5. 打印一个字符串"$text | $json_obj_value"\(foo)这是你如何进行插值jq

如果你想让它尽可能紧凑,你可以使用

jq -Rr 'select(contains(" | "))|split(" | ")|"\(.[0]) | \(.[1]|fromjson|to_entries|.[0].value)"' yourlogfile.log

这会更小,但也更难阅读。哪一个最好取决于品味和用例。

相关内容