各行に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レコードデータを処理するにはどうすればよいですか?
- 使用 --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})'
@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つの主な利点があります。
group_by/1
_とは異なり、ソート操作を必要としないため、潜在的に高速です。上記の_GROUPS_BY/2
_の定義は、ストリームを生成するという点で、このようなストリーミングフィルターの規則に従っていることに注意してください。もちろん、他のバリエーションも可能です。
以下は、メモリを節約する方法を示しています。タスクが.id値の頻度カウントを生成することであると仮定します。ハムドラムの解決策は次のようになります。
_GROUPS_BY(inputs; .id) | [(.[0]|.id), length]
_
より経済的で実際にはるかに優れたソリューションは次のとおりです。
_GROUPS_BY(inputs|.id; .) | [.[0], length]
_