JSONデータの操作にjqコマンド使ってみたら扱いやすかったので紹介したい

2021.07.09

はじめに

前回の記事でAWS CLIコマンドのJSONデータをjqコマンドで操作したら大変便利だったので紹介します。

jqコマンドとは

JSONデータをsedやgrep、awkのようにデータ抽出、変換、集計してくれるツールです。

AWS CLIのの出力形式は、複数の出力形式がサポートされていますが、デフォルトではJSONで出力されます。

AWS CLIでも--filters--queryを使えば抽出や変換できますが、AWS CLI以外のJSONデータ(公開されている様々なAPIが、ほぼJSONで出力)を操作するとき、jqコマンドのようなツールを覚えておくと便利かなと考えました。

公開API(例)

使い方

jqは各種OSにインストールして利用できます。今回はmacOS Catalinaにインストールした環境で操作していきます。

バージョン確認コマンドが出力されればOKです。

% jq --version
jq-1.6

jqで操作するJSONデータは、aws ec2 describe-vpcsで出力されたデータをローカルに保存しておきます。AWS CLIコマンドをパイプで渡して操作もできるので都度ローカルに保存する必要はありません。

describe-instances.json
{
    "Vpcs": [
        {
            "CidrBlock": "10.255.0.0/16",
            "DhcpOptionsId": "dopt-77e9dxxx",
            "State": "available",
            "VpcId": "vpc-029f2873de5756xxx",
            "OwnerId": "xxxxxxxxxxxx",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
                    "CidrBlock": "10.255.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false
        },
        {
            "CidrBlock": "10.0.0.0/16",
            "DhcpOptionsId": "dopt-87e9dxxx",
            "State": "available",
            "VpcId": "vpc-354f2873de5162xxx",
            "OwnerId": "xxxxxxxxxxxx",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
                    "CidrBlock": "10.0.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false
        }
    ]
}

シンプルな出力

まずはシンプルにjqコマンドを実行してみます。

% cat ./describe-vpcs.json | \
  jq '.'
{
  "Vpcs": [
    {
      "CidrBlock": "10.255.0.0/16",
      "DhcpOptionsId": "dopt-77e9dxxx",
      "State": "available",
      "VpcId": "vpc-029f2873de5756xxx",
      "OwnerId": "xxxxxxxxxxxx",
      "InstanceTenancy": "default",
      "CidrBlockAssociationSet": [
        {
          "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
          "CidrBlock": "10.255.0.0/16",
          "CidrBlockState": {
            "State": "associated"
          }
        }
      ],
      "IsDefault": false
    },
    {
      "CidrBlock": "10.0.0.0/16",
      "DhcpOptionsId": "dopt-87e9dxxx",
      "State": "available",
      "VpcId": "vpc-354f2873de5162xxx",
      "OwnerId": "xxxxxxxxxxxx",
      "InstanceTenancy": "default",
      "CidrBlockAssociationSet": [
        {
          "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
          "CidrBlock": "10.0.0.0/16",
          "CidrBlockState": {
            "State": "associated"
          }
        }
      ],
      "IsDefault": false
    }
  ]
}

catコマンドと出力結果は変わりませんが、厳密には標準出力された内容をJSONで標準出力しています。引数'.'が、JSONオブジェクト(ブレース{で始まりブレース}で終わる)を表しています。

配列を指定した出力

インデックス(0から始まる)を指定して抽出できます。describe-vpcs.jsonには、VPcsに2つの配列(0,1)があるので、ブラケット[][0]を指定して抽出してみます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[0]'
{
  "CidrBlock": "10.255.0.0/16",
  "DhcpOptionsId": "dopt-77e9dxxx",
  "State": "available",
  "VpcId": "vpc-029f2873de5756xxx",
  "OwnerId": "xxxxxxxxxxxx",
  "InstanceTenancy": "default",
  "CidrBlockAssociationSet": [
    {
      "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
      "CidrBlock": "10.255.0.0/16",
      "CidrBlockState": {
        "State": "associated"
      }
    }
  ],
  "IsDefault": false
}

指定したインデックスにデータが存在しない場合は、Nullが返されます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[2]'
null

範囲を指定した出力

範囲を指定して配列を抽出できます。
ブラケット[][0:2]では、0以上2未満で抽出されます。範囲の開始を指定しない[:2]の場合は、0から開始されるので先程とおなじく0以上2未満で抽出されます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[0:2]'
[
  {
    "CidrBlock": "10.255.0.0/16",
    "DhcpOptionsId": "dopt-77e9dxxx",
    "State": "available",
    "VpcId": "vpc-029f2873de5756xxx",
    "OwnerId": "xxxxxxxxxxxx",
    "InstanceTenancy": "default",
    "CidrBlockAssociationSet": [
      {
        "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
        "CidrBlock": "10.255.0.0/16",
        "CidrBlockState": {
          "State": "associated"
        }
      }
    ],
    "IsDefault": false
  },
  {
    "CidrBlock": "10.0.0.0/16",
    "DhcpOptionsId": "dopt-87e9dxxx",
    "State": "available",
    "VpcId": "vpc-354f2873de5162xxx",
    "OwnerId": "xxxxxxxxxxxx",
    "InstanceTenancy": "default",
    "CidrBlockAssociationSet": [
      {
        "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
        "CidrBlock": "10.0.0.0/16",
        "CidrBlockState": {
          "State": "associated"
        }
      }
    ],
    "IsDefault": false
  }
]
% cat ./describe-vpcs.json | \
  jq '.Vpcs[:2]'
[
  {
    "CidrBlock": "10.255.0.0/16",
    "DhcpOptionsId": "dopt-77e9dxxx",
    "State": "available",
    "VpcId": "vpc-029f2873de5756xxx",
    "OwnerId": "xxxxxxxxxxxx",
    "InstanceTenancy": "default",
    "CidrBlockAssociationSet": [
      {
        "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
        "CidrBlock": "10.255.0.0/16",
        "CidrBlockState": {
          "State": "associated"
        }
      }
    ],
    "IsDefault": false
  },
  {
    "CidrBlock": "10.0.0.0/16",
    "DhcpOptionsId": "dopt-87e9dxxx",
    "State": "available",
    "VpcId": "vpc-354f2873de5162xxx",
    "OwnerId": "xxxxxxxxxxxx",
    "InstanceTenancy": "default",
    "CidrBlockAssociationSet": [
      {
        "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
        "CidrBlock": "10.0.0.0/16",
        "CidrBlockState": {
          "State": "associated"
        }
      }
    ],
    "IsDefault": false
  }
]

後方から範囲指定する場合は[-1:]として抽出することもできます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[-1:]'
[
  {
    "CidrBlock": "10.0.0.0/16",
    "DhcpOptionsId": "dopt-87e9dxxx",
    "State": "available",
    "VpcId": "vpc-354f2873de5162xxx",
    "OwnerId": "xxxxxxxxxxxx",
    "InstanceTenancy": "default",
    "CidrBlockAssociationSet": [
      {
        "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
        "CidrBlock": "10.0.0.0/16",
        "CidrBlockState": {
          "State": "associated"
        }
      }
    ],
    "IsDefault": false
  }
]

なお、範囲指定でインデックスにデータがない場合は何も表示されず、nullが返されません。

最後尾から出力

配列のインデックスをreverseして抽出できます。範囲を指定した出力と一緒でreverse[]のブラケットにインデックスや範囲を指定して抽出できます。

% cat ./describe-vpcs.json | \
  jq '.[] | reverse[]'
{
  "CidrBlock": "10.0.0.0/16",
  "DhcpOptionsId": "dopt-87e9dxxx",
  "State": "available",
  "VpcId": "vpc-354f2873de5162xxx",
  "OwnerId": "xxxxxxxxxxxx",
  "InstanceTenancy": "default",
  "CidrBlockAssociationSet": [
    {
      "AssociationId": "vpc-cidr-assoc-0e99eaeae46622xxx",
      "CidrBlock": "10.0.0.0/16",
      "CidrBlockState": {
        "State": "associated"
      }
    }
  ],
  "IsDefault": false
}
{
  "CidrBlock": "10.255.0.0/16",
  "DhcpOptionsId": "dopt-77e9dxxx",
  "State": "available",
  "VpcId": "vpc-029f2873de5756xxx",
  "OwnerId": "xxxxxxxxxxxx",
  "InstanceTenancy": "default",
  "CidrBlockAssociationSet": [
    {
      "AssociationId": "vpc-cidr-assoc-0e18eaeae48822xxx",
      "CidrBlock": "10.255.0.0/16",
      "CidrBlockState": {
        "State": "associated"
      }
    }
  ],
  "IsDefault": false
}

値の出力

各配列にある値を抽出する場合は、JSONデータ構造を意識して抽出します。VpcsのCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[].CidrBlock'
"10.255.0.0/16"
"10.0.0.0/16"

インデックスを指定しなければ、すべての配列からCidrBlock値のValueが抽出されます。
また、ネストされた配列からも抽出できます。CidrBlockAssociationSetのCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[].CidrBlockAssociationSet[].CidrBlock'
"10.255.0.0/16"
"10.0.0.0/16"

値の出力(フィルタリング)

select

jqには、様々な組み込み演算子や関数が用意されています。先程の値の抽出で特定の値だけ抽出する場合は、selectを使ってフィルタリングできます。 CidrBlock値が10.255.0.0/16と一致する配列からCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock == "10.255.0.0/16") | .CidrBlock ' 
"10.255.0.0/16"

contains

CidrBlock値に16を含む配列からCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| contains ("16")) | .CidrBlock '
"10.255.0.0/16"
"10.0.0.0/16"

startswith

10.255から始まるCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| startswith ("10.255")) | .CidrBlock '

endswith

16で終わるCidrBlock値を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| endswith ("16")) | .CidrBlock '

AND OR

ANDやOR条件も使えます。10.255から始まる且つ、16で終わる配列を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| startswith ("10.255") and endswith ("16")) | .CidrBlock '
"10.255.0.0/16"

10.255から始まるまたは16で終わる配列を抽出します。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| startswith ("10.255") or endswith ("16")) | .CidrBlock '
"10.255.0.0/16"
"10.0.0.0/16"

出力結果を整形

フィルタリングした内容を整形することができます。先程の出力だと値だけ出力されるので{ 任意キー(String): .CidrBlock }を追加して出力できます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| startswith ("10.255") or endswith ("16")) | { CidrBlock: .CidrBlock } '
{
  "CidrBlock": "10.255.0.0/16"
}
{
  "CidrBlock": "10.0.0.0/16"
}

VpcIdも一緒に出力してみます。カンマ,で区切ればいくつも追加して出力できます。

% cat ./describe-vpcs.json | \
  jq '.Vpcs[] | select( .CidrBlock| startswith ("10.255") or endswith ("16")) | { CidrBlock: .CidrBlock, VpcId: .VpcId} '
{
  "CidrBlock": "10.255.0.0/16",
  "VpcId": "vpc-029f2873de5756xxx"
}
{
  "CidrBlock": "10.0.0.0/16",
  "VpcId": "vpc-354f2873de5162xxx"
}

RAWデータで出力

jqはJSON形式で出力するので抽出された最小の値でもダブルクォーテーションで括られたデータで出力されます。jqコマンドにオプション-rを追加するとRAWデータで出力されます。

% cat ./describe-vpcs.json | \
  jq -r '.Vpcs[] | select( .CidrBlock| startswith ("10.255") and endswith ("16")) | .CidrBlock '
10.255.0.0/16

さいごに

JSONデータを良い感じに操作できるjqコマンドの紹介でした。基本的な一部の出力方法をご案内しましたが、まだまだいろんな機能があります。詳しくは公式のManualを参照してください。

jqをインストールする前にどんな感じで抽出されるか確認したい場合は、以下のページでも確認できます。ただ、入力したデータの取り扱いに関する記載が見つけられなかったので、データをマスキングしてお試しください。