エンジニアになって以来あまりシェルを触ることがなく、必要に迫られてシェルを改めて勉強し始めてからは、シェルの表現力の高さをようやく実感しています。
今回はコマンドラインでJSONを操作するjq
で少し複雑な条件式を書く時のサンプルをまとめてみました。
selectでフィルタリングをする
まずはこのようなJSONデータを用意します。(今回はスペースの都合上、配列に一つの要素だけを含みますが、実際には不特定多数の要素が入っていることを想定します)
{
"items": [
{
"rendered_body": "<h1>Example</h1>",
"body": "# Example",
"coediting": false,
"comments_count": 100,
"created_at": "2000-01-01T00:00:00+00:00",
"group": {
"created_at": "2000-01-01T00:00:00+00:00",
"id": 1,
"name": "Dev",
"private": false,
"updated_at": "2000-01-01T00:00:00+00:00",
"url_name": "dev"
},
"id": "4bd431809afb1bb99e4f",
"likes_count": 100,
"private": false,
"reactions_count": 100,
"tags": [
{
"name": "Ruby",
"versions": [
"0.0.1"
]
}
]
}
]
}
(※JSONの値はQiitaのAPIサンプルから拝借しました)
jqではフィルタリングをする際にselect
句を使用しますが、これは表現力が高く、配列中の値合致も以下のように簡単に書くことができます。
$ cat data.json | jq '.items[] | select(.body == "# Example")'
上記のコマンドを実行すると配列中にあるbody
キーが# Example
となるオブジェクトをフィルタリングしてくれます。
もちろんネストしても同じようにフィルタリング可能で、直感的に操作できるのが良いですね👍
$ cat data.json | jq '.items[] | select(.tags[].name == "Ruby")'
AND / OR検索
select
句はAND/ORで複合検索することも可能です。例えば「bodyが# Examples
かつtagにRuby
を含むもの」という時は以下のように指定します。
$ cat data.json | jq '.items[] | select(.body == "# Example" and .tags[].name == "Ruby")'
OR検索の場合はand
の箇所をor
に変更するのみです。
$ cat data.json | jq '.items[] | select(.body == "# Example" or .tags[].name == "Ruby")'
これらは通常のプログラミング言語と同様に、ANDとORを同時かつ複合的に利用することもできます。
contains / startswith / endswith
jqには便利な構文がいくつも用意されており、状況に応じてそれらを活用するとシンプルに記述することができます。
例えば「含まれる」を表すcontains
は以下のようになります。
$ cat data.json | jq '.items[] | select(.body | contains("Example"))'
同様に「〜から始まる」を表すstartswith
、「〜で終わる」を表すendswith
はそれぞれ次のようになります。
$ cat data.json | jq '.items[] | select(.body | startswith("#"))'
$ cat data.json | jq '.items[] | select(.body | endswith("e"))'
その他に使える構文についてはこちらの公式ドキュメントを参照してみてください。
正規表現を活用する
さらに複雑なフィルタリングを行いたい場合は正規表現を使ってみましょう。
jqで正規表現を使うにはtest
とmatch
と2つの方法があります。testはシンプルに正規表現に合致するものを探す場合、matchはマッチした位置などを取得する場合に利用します。
なお今回はjsonのフィルタリングを行うので、testを使えば要件として問題ありません。例えば「タグ名にPythonまたはJavaScriptが含まれる」という条件を表現するには次のようになります。
$ cat data.json | jq '.items[] | select(.tags[].name | test("(Python|JavaScript)"))'
もちろん行先頭の^
や行末尾の$
なども利用できるので、必要に応じて使ってみてください。
ちなみにmatch
を使うとより詳細な一致情報が取得できます。
$ echo '"Peter"' | jq 'match("(Pe)(ter)")'
こちらの出力結果は
{
"offset": 0,
"length": 5,
"string": "Peter",
"captures": [
{
"offset": 0,
"length": 2,
"string": "Pe",
"name": null
},
{
"offset": 2,
"length": 3,
"string": "ter",
"name": null
}
]
}
以上のようにoffset
、length
、string
、captures
が取得でき、capturesは()
で囲った部分が配列として格納されます。
最後に
ここまで紹介したようにjq
を使用すると、コマンドラインで完結するにも関わらずスクリプト言語以上の表現力が得られるほか、高いパフォーマンス要件も満たせます。
JSONデータを処理する場合はぜひ活用してみて下さい。