web-dev-qa-db-ja.com

jqを使用して1行あたりのJSONレコードを解析しますか?

各行に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を取得して1行あたりのJSONレコードデータを処理するにはどうすればよいですか?

8
Roger Lipscombe

- 使用 --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})'
11
Roger Lipscombe

@JeffMercadoが指摘したように、jqはJSONのストリームを問題なく処理しますが、_group_by_を使用する場合は、入力が配列であることを確認する必要があります。この場合、これは_-s_コマンドラインオプションを使用して実行できます。 jqにinputsフィルターがある場合は、そのフィルターを_-n_オプションと組み合わせて使用​​することもできます。

ただし、inputs(jq 1.5で使用可能)のバージョンのjqがある場合は、次の_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_コマンドラインオプションで使用することに注意してください。

このようなストリーミングバリアントには、2つの主な利点があります。

  1. 処理中に入力ストリーム全体のコピーをメモリに保持する必要がないという点で、一般的に必要なメモリは少なくなります。
  2. _group_by/1_とは異なり、ソート操作を必要としないため、潜在的に高速です。

上記の_GROUPS_BY/2_の定義は、ストリームを生成するという点で、このようなストリーミングフィルターの規則に従っていることに注意してください。もちろん、他のバリエーションも可能です。

大量のデータの処理

以下は、メモリを節約する方法を示しています。タスクが.id値の頻度カウントを生成することであると仮定します。ハムドラムの解決策は次のようになります。

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

より経済的で実際にはるかに優れたソリューションは次のとおりです。

_GROUPS_BY(inputs|.id; .) | [.[0], length]
_
4
peak