📝

jqでJSONをパス形式に変換してgrepしやすくする

に公開

簡単に述べるとjqでgronのようなことをします。

例えばdocker image inspectで得られるJSONのどこにコンテナ起動時に実行されるコマンドが書かれているか確認したいとき、単にdocker image inspectの結果をgrepすると次のようになります。

$ docker image inspect sonatype/nexus3 | grep -i cmd
            "Cmd": [

これではJSONのどこに書かれているのかはわかりません。

そこで次のjqコマンドを使用します。

jq -r 'paths(scalars) as $p | ($p | join(".")) + " = " + (getpath($p) | @json)'

先ほどのdocker image inspectに適用すると次のようになります。

$ docker image inspect sonatype/nexus3 | jq -r 'paths(scalars) as $p | ($p | join(".")) + " = " + (getpath($p) | @json)' | grep -i cmd
0.Config.Cmd.0 = "/opt/sonatype/nexus/bin/nexus"
0.Config.Cmd.1 = "run"

得られた結果から、コマンドが書かれている場所は.[0].Config.Cmdであることがわかります。

なお、ここで得られた0.Config.Cmdという表現はそのままではjqに渡せず、.[0].Config.Cmdというふうに変換する必要があります。

$ docker image inspect sonatype/nexus3  | jq '0.Config.Cmd'   
jq: error: syntax error, unexpected IDENT, expecting end of file at <top-level>, line 1, column 3:
    0.Config.Cmd
      ^^^^^^
jq: 1 compile error
$ docker image inspect sonatype/nexus3  | jq '.[0].Config.Cmd'
[
  "/opt/sonatype/nexus/bin/nexus",
  "run"
]

もし変換の手間を省くならjqコマンドは次のようになります。

jq -r 'paths(scalars) as $p | "." + ($p | map(if (. | type) == "number" then "[\(.)]" elif (. | contains(".")) then "[\"\(.)\"]" else . end) | join(".")) + " = " + (getpath($p) | @json)'

実際に試すと次のようになります。

$ docker image inspect sonatype/nexus3 | jq -r 'paths(scalars) as $p | "." + ($p | map(if (. | type) == "number" then "[\(.)]" elif (. | contains(".")) then "[\"\(.)\"]" else . end) | join(".")) + " = " + (getpath($p) | @json)' | grep -i cmd
.[0].Config.Cmd.[0] = "/opt/sonatype/nexus/bin/nexus"
.[0].Config.Cmd.[1] = "run"

jqに渡せる表現になりました。

以上で期待する結果が得られましたが、この長くて複雑なjqコマンドはモジュール化することで再利用しやすくなります。
手順は次の通りです。

次の内容を$HOME/.jq/util.jqへ保存します。

def _convert_key:
    if (. | type) == "number" then "[\(.)]"
    elif (. | contains(".")) then "[\"\(.)\"]"
    else . end;

def _key($p):
    "." + ($p | map(_convert_key) | join("."));

def _value($p):
    getpath($p) | @json;

def pathlist:
    paths(scalars) as $p
    | _key($p) + " = " + _value($p);

次のように使います。

$ docker image inspect sonatype/nexus3 | jq -r 'import "util" as u; u::pathlist' | grep -i cmd
.[0].Config.Cmd.[0] = "/opt/sonatype/nexus/bin/nexus"
.[0].Config.Cmd.[1] = "run"

Discussion