LoginSignup
81

More than 1 year has passed since last update.

jqを使って少し複雑な条件式でフィルタリングする方法をまとめてみた

Last updated at Posted at 2019-05-20

エンジニアになって以来あまりシェルを触ることがなく、必要に迫られてシェルを改めて勉強し始めてからは、シェルの表現力の高さをようやく実感しています。
今回はコマンドラインでJSONを操作するjqで少し複雑な条件式を書く時のサンプルをまとめてみました。

selectでフィルタリングをする

まずはこのようなJSONデータを用意します。(今回はスペースの都合上、配列に一つの要素だけを含みますが、実際には不特定多数の要素が入っていることを想定します)

data.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で正規表現を使うにはtestmatchと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
    }
  ]
}

以上のようにoffsetlengthstringcapturesが取得でき、capturesは()で囲った部分が配列として格納されます。

最後に

ここまで紹介したようにjqを使用すると、コマンドラインで完結するにも関わらずスクリプト言語以上の表現力が得られるほか、高いパフォーマンス要件も満たせます。

JSONデータを処理する場合はぜひ活用してみて下さい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81