Featured image of post jq/yqでオブジェクトのキーを正規表現でフィルタする

jq/yqでオブジェクトのキーを正規表現でフィルタする

あんまり出番はないかもしれないが...

まとめ

jq(yq)のwith_entries, select, testを組み合わせるとオブジェクトのキー名の正規表現でフィルタをかけることができる。

# regexには正規表現のパターンを記述する
jq "with_entries(select(.key|test(\"${regex}\")))"
yq "with_entries(select(.key|test(\"${regex}\")))" 

環境

キーに正規表現を当てる

こんな感じのJSONまたはYAMLについて、.config配下のspec111spec222の要素だけを正規表現で抜きたいとする。

こんなときはwith_entriesselect, testを使うと良い感じにできる。

# 先頭がspecで始まり数字で終わるパターン
$ regex='^spec[0-9]+$'

$ cat example.json | jq ".config | with_entries(select(.key|test(\"${regex}\")))"

{
  "spec111": {
    "aaa": 123,
    "bbb": 456
  },
  "spec222": {
    "aaa": "abc",
    "bbb": "def"
  }
}

$ cat example.yaml | yq ".config | with_entries(select(.key|test(\"${regex}\")))" 

spec111:
  aaa: 123
  bbb: 456
spec222:
  aaa: abc
  bbb: def

なにが起こっているのか

jq Manualによるとwith_entries(foo)
to_entries | map(foo) | from_entriesと同じ意味なので、
先ほどのコマンドを下記のように分解して何が起こっているのか詳細を追っていく。
(yqについてもjqとほぼ同じはず)

# 下記2行は同じ操作
jq ".config | with_entries(select(.key|test(\"${regex}\")))"
jq ".config | to_entries | map(select(.key|test(\"${regex}\"))) | from_entries"

# yqについても同じ
yq ".config | with_entries(select(.key|test(\"${regex}\")))" 
yq ".config | to_entries | map(select(.key|test(\"${regex}\"))) | from_entries"

まずはto_entriesから。

これを噛ませるとキーの文字列に.keyとしてアクセスできるようになる。
https://stedolan.github.io/jq/manual/#to_entries,from_entries,with_entries
https://mikefarah.gitbook.io/yq/operators/entries

$ cat example.json | jq ".config | to_entries"                                                      

[
  {
    "key": "spec111",
    "value": {
      "aaa": 123,
      "bbb": 456
    }
  },
  {
    "key": "spec222",
    "value": {
      "aaa": "abc",
      "bbb": "def"
    }
  },
  {
    "key": "metadata",
    "value": {
      "hoge": "fuga"
    }
  }
]

$ cat example.yaml | yq ".config | to_entries"

- key: spec111
  value:
    aaa: 123
    bbb: 456
- key: spec222
  value:
    aaa: abc
    bbb: def
- key: metadata
  value:
    hoge: fuga

to_entriesを噛ませた結果は配列として出力されるので、
この配列の要素を正規表現でフィルタするためにmap, select, testを噛ませてやる。

https://stedolan.github.io/jq/manual/#map(x),map_values(x)
https://stedolan.github.io/jq/manual/#select(boolean_expression)
https://stedolan.github.io/jq/manual/#test(val),test(regex;flags)

https://mikefarah.gitbook.io/yq/operators/map
https://mikefarah.gitbook.io/yq/operators/select
https://mikefarah.gitbook.io/yq/operators/string-operators#test-using-regex

select, testの条件にto_entriesで追加された.keyを指定することでキー名で正規表現をかけることができる。

$ regex='^spec[0-9]+$'

$ cat example.json | jq ".config | to_entries | map(select(.key|test(\"${regex}\")))"

[
  {
    "key": "spec111",
    "value": {
      "aaa": 123,
      "bbb": 456
    }
  },
  {
    "key": "spec222",
    "value": {
      "aaa": "abc",
      "bbb": "def"
    }
  }
]

$ cat example.yaml | yq ".config | to_entries | map(select(.key|test(\"${regex}\")))"               

- key: spec111
  value:
    aaa: 123
    bbb: 456
- key: spec222
  value:
    aaa: abc
    bbb: def

あとはto_entriesのフォーマットの配列をfrom_entriesで元のキーと値の形式に戻してやる。

$ regex='^spec[0-9]+$'

$ cat example.json | jq ".config | to_entries | map(select(.key|test(\"${regex}\"))) | from_entries"

{
  "spec111": {
    "aaa": 123,
    "bbb": 456
  },
  "spec222": {
    "aaa": "abc",
    "bbb": "def"
  }
}

$ cat example.yaml | yq ".config | to_entries | map(select(.key|test(\"${regex}\"))) | from_entries"

spec111:
  aaa: 123
  bbb: 456
spec222:
  aaa: abc
  bbb: def

最初のコマンドと同じ結果が得られた。

おわり

jq(yq)でオブジェクトのキーに対して正規表現でフィルタをかけることができた。
マジで便利。

おまけ

こたつで溶けるねこ