14

我有一个在每一行输出 JSON 记录的工具,我想用jq.

输出看起来像这样:

{"ts":"2017-08-15T21:20:47.029Z","id":"123","elapsed_ms":10}
{"ts":"2017-08-15T21:20:47.044Z","id":"456","elapsed_ms":13}

当我将其传递给jq如下:

./tool | jq 'group_by(.id)'

...它输出一个错误:

jq: error (at <stdin>:1): Cannot index string with string "id"

如何jq处理 JSON-record-per-line 数据?

4

2 回答 2

14

使用--slurp(或-s)开关:

./tool | jq --slurp 'group_by(.id)'

它输出以下内容:

[
  [
    {
      "ts": "2017-08-15T21:20:47.029Z",
      "id": "123",
      "elapsed_ms": 10
    }
  ],
  [
    {
      "ts": "2017-08-15T21:20:47.044Z",
      "id": "456",
      "elapsed_ms": 13
    }
  ]
]

...然后您可以进一步处理。例如:

./tool | jq -s 'group_by(.id) | map({id: .[0].id, count: length})'
于 2017-08-16T13:08:18.293 回答
4

正如@JeffMercado 指出的那样, jq 可以很好地处理 JSON 流,但是如果您使用group_by,那么您必须确保其输入是一个数组。在这种情况下,可以使用-s命令行选项来完成;如果您的 jq 有inputs过滤器,那么也可以将该过滤器与该-n选项结合使用。

但是,如果您有一个 jq 版本inputs(在 jq 1.5 中可用),那么更好的方法是使用以下流式变体group_by

 # sort-free stream-oriented variant of group_by/1
 # f should always evaluate to a string.
 # Output: a stream of arrays, one array per group
 def GROUPS_BY(stream; f): reduce stream as $x ({}; .[$x|f] += [$x] ) | .[] ;

使用示例:GROUPS_BY(inputs; .id)

请注意,您将希望将其与-n命令行选项一起使用。

这种流式传输变体有两个主要优点:

  1. 它通常需要较少的内存,因为它不需要在处理整个输入流时将整个输入流的副本保存在内存中;
  2. 它可能更快,因为它不需要任何排序操作,不像group_by/1.

请注意,上面的定义GROUPS_BY/2遵循这种流过滤器的约定,因为它产生一个流。其他变体当然是可能的。

处理大量数据

下面说明如何节省内存。假设任务是生成 .id 值的频率计数。单调的解决方案是:

GROUPS_BY(inputs; .id) | [(.[0]|.id), length]

一个更经济且确实更好的解决方案是:

GROUPS_BY(inputs|.id; .) | [.[0], length]
于 2017-08-16T14:04:14.927 回答