jq は CLI で使用できる JSON processor でみなさんも日々お使いのことと存じます。
問題
jq は標準入力に対して様々なフィルタや加工を行えるわけですが、基本的にはその入力フォーマットが JSON になっていることがベースになります。 このため、以下のように JSON の形ではない行が混ざると、問題になります。
{"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} hoge {"level":"info","msg":"hello world","time":"2019-09-01T06:58:28.99333+09:00"} fuga {"level":"info","msg":"hello world","time":"2019-09-01T07:03:28.99333+09:00"}
例えば上記のファイルを jq に通すと、エラーになります。
$ cat log.json | jq -c '.' {"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} parse error: Invalid numeric literal at line 3, column 0
最近は標準出力に対して様々なものが混ざってくるので、jq で処理できる行だけを処理し、処理できない行でエラーを起こしたくない。こういうときにどうすれば良いのかという話です。
解法
上記のようなケースにおいては、ぼくは以下のように jq -R 'fromjson?'
一度通すようにしています。
$ cat log.json | jq -cR 'fromjson?' {"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T06:58:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T07:03:28.99333+09:00"}
あとは後段に、jq を含む任意のコマンドをつなげれば良いですね。
解説
まず、-R
(--raw-input
) を使うことで、input を直接 JSON として parse しないようにし、文字列がそのまま jq のフィルタに渡るようにします。こうすることで、jq
の入力処理でエラーになりません。
しかし、フィルタ部に渡るのは文字列ですから、これを JSON 化しなければなりません。これは fromjson
で可能です。しかし、fromjson
だけだと、JSON でない文字列に対してエラーが発生してしまいます。
$ cat log.json | jq -cR 'fromjson' {"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} jq: error (at <stdin>:2): Invalid numeric literal at EOF at line 1, column 4 (while parsing 'hoge') {"level":"info","msg":"hello world","time":"2019-09-01T06:58:28.99333+09:00"} jq: error (at <stdin>:4): Invalid literal at EOF at line 1, column 4 (while parsing 'fuga') {"level":"info","msg":"hello world","time":"2019-09-01T07:03:28.99333+09:00"}
ここで fromjson
の最後に ?
を加えます。これは、jq におけるエラー抑制の省略形ですね。
$ cat log.json | jq -cR 'fromjson?' {"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T06:58:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T07:03:28.99333+09:00"}
省略しなければ以下のような形でしょうか。
$ cat log.json | jq -cR 'try fromjson?' {"level":"info","msg":"hello world","time":"2019-09-01T07:01:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T06:58:28.99333+09:00"} {"level":"info","msg":"hello world","time":"2019-09-01T07:03:28.99333+09:00"}