aws cli と jq コマンド

こちらの記事は [ケーシーエスキャロット Advent Calendar 2020](https://qiita.com/advent-calendar/2020/kcscarrot) の20日目の記事です。

昨日は `aooki` さんの [WMS の地図画像を Google Map 上に表示する](http://aoiro6.hatenablog.com/entry/2020/12/18/184810) でした、クロスブラウザ問題まだまだあるんですね…。

さて皆さん、`aws cli` は使用していますか？
AWSコンソールで見れば手軽に状況や結果は確認できますが、複数の確認を行う時はポチポチを複数行わないといけませんよね。

そんな時、`aws cli` を使用すれば、複数の結果を `json` でひとまとめで確認できます。
…が、結果が多数あると確認作業が大変なことは変わりません。

例えば、`aws ec2 describe-instances` のコマンドを使用すればインスタンス情報が取得出来ますが、色々な情報を出力されすぎて対象のデータを目視で探し出すのも大変です。

そんな時、`jq` コマンドを使用すれば特定の情報を抜き出すことが出来ます。

### jq

`json` データの加工・フィルターが行えるコマンド。
オフィシャルのマニュアルは[こちら](https://stedolan.github.io/jq/manual/)を参照。

### 準備

まずはインストールから始めましょう。
`Windows10` の `WSL(ubuntu18.04)` 環境下へインストールしてみます。

```bash
$ sudo apt -y install jq
$ jq --version
jq-1.5-1-a5b5cbe
```

### 基本編

`jq` での基本的なコマンドは、

1. `.` (ルート)
1. `[]` (リスト)

の2つです。

また、`|` (パイプ)を繋げることによって、下階層のデータに対して操作が行えます。

```json:test1.json
{
    "schema": "value",
    "list": [
        {
            "param_a": 111
        }, 
        {
            "param_a": 222
        }
    ]
}
```

例えば、上記のような `json` があったとして `schema` キーの値を取得したい場合は以下のようにします。

```bash
$ cat test1.json | jq '.schema'
"value"
```

また、代わりに `list` キーを指定すると以下になります。

```bash
$ cat test1.json | jq '.list'
[
  {
    "param_a": 111
  },
  {
    "param_a": 222
  }
]
```

さらに `|` で繋げて `.[]` を指定すると以下の結果になります。

```bash
$ cat test1.json  | jq '.list | .[]'
{
  "param_a": 111
}
{
  "param_a": 222
}
```

※ 一見、違いが分かりにくいですが、最上位の `[]` が外れています。

さらにさらに `param_a` を `|` で繋げると以下の結果となります。

```bash
$ cat test1.json | jq '.list | .[] | .param_a'
111
222
```

### 応用編

前述した `aws ec2 describe-instances` コマンドで取得できる `json` データから一部抜粋した以下の内容を例に進めます。

```json:test2.json
{
    "Reservations": [
        {
            "Instances": [
                {
                    "InstanceId": "i-111",
                    "BlockDeviceMappings": [
                        {
                            "DeviceName": "/dev/xvda1",
                            "Ebs": {
                                "VolumeId": "vol-111aaa"
                            }
                        },
                        {
                            "DeviceName": "/dev/xvda2",
                            "Ebs": {
                                "VolumeId": "vol-111bbb"
                            }
                        }
                    ]
                },
                {
                    "InstanceId": "i-222",
                    "BlockDeviceMappings": [
                        {
                            "DeviceName": "/dev/xvda1",
                            "Ebs": {
                                "VolumeId": "vol-222ccc"
                            }
                        },
                        {
                            "DeviceName": "/dev/xvda2",
                            "Ebs": {
                                "VolumeId": "vol-222ddd"
                            }
                        }
                    ]
                }
            ]
        }
    ]
}
```

例えば、`VolumeId` の値を列挙しようとした場合、基本編で記載した通り `|` で繋げていけば取得出来ます。

```bash
$ cat test2.json | jq '.Reservations | .[] | .Instances | .[] | .BlockDeviceMappings | .[] | .Ebs | .VolumeId'
"vol-111aaa"
"vol-111bbb"
"vol-222ccc"
"vol-222ddd"
```

因みに `.xxx | .[]` は `.xxx[]` と記載できるので、以下でも同じ結果が得られます。

```bash
$ cat test2.json  | jq '.Reservations[] | .Instances[] | .BlockDeviceMappings[] | .Ebs | .VolumeId'
"vol-111aaa"
"vol-111bbb"
"vol-222ccc"
"vol-222ddd"
```

もっと言えば、`|` で繋げて記載しなくても実現出来たりますが、`|` で区切った方が階層が分かりやすいようにも思います。(この辺りは好みの問題ですね)

```bash
$ cat test2.json | jq '.Reservations[].Instances[].BlockDeviceMappings[].Ebs.VolumeId'
"vol-111aaa"
"vol-111bbb"
"vol-222ccc"
"vol-222ddd"
```

もう少し発展させて、`DeviceName` キーの値が `/dev/xvda1` の `VolumeId` 値のみを取得したい場合はどうすれば良いでしょう？
答えは、`select` を使用すれば実現できます。

```bash
$ cat test2.json  | jq '.Reservations[] | .Instances[] | .BlockDeviceMappings[] | select(.DeviceName == "/dev/xvda1") | .Ebs.VolumeId'
"vol-111aaa"
"vol-222ccc"
```

※ `select` で `DeviceName` が `/dev/xvda1` のもののみ抽出しています。

さらに発展させて一緒に `InstanceId` も取得する場合は以下となります。

```bash
$ cat test2.json  | jq '.Reservations[] | .Instances[] | .InstanceId, (.BlockDeviceMappings[] | select(.Devi
ceName == "/dev/xvda1") | .Ebs.VolumeId)'
"i-111"
"vol-111aaa"
"i-222"
"vol-222ccc"
```

※ `InstanceId` と `,` で繋げた上で `VolumeId` は `()` で括りました。

でも、この出力結果だと分かりづらいですね。
`InstanceId,VolumeId` のように csv 形式で出力してみたいと思います、最後に `@csv` を付けると実現できます。

```bash
$ cat test2.json  | jq '.Reservations[] | .Instances[] | [.InstanceId, (.BlockDeviceMappings[] | select(.Dev
iceName == "/dev/xvda1") | .Ebs.VolumeId)] | @csv'
"\"i-111\",\"vol-111aaa\""
"\"i-222\",\"vol-222ccc\""
```

※ 取得する項目は `[]` で括っていますが、`@csv` で出力するには `array` 形式でないといけないからです。

一応、csv 形式で出力されましたが、`"` が邪魔ですね…。
そんな場合は、`-r` を指定すると取り除けます。

```bash
$ cat test2.json  | jq -r '.Reservations[] | .Instances[] | [.InstanceId, (.BlockDeviceMappings[] | select(.
DeviceName == "/dev/xvda1") | .Ebs.VolumeId)] | @csv'
"i-111","vol-111aaa"
"i-222","vol-222ccc"
```

### まとめ

いかがでしたでょうか？
`aws cli` からデータを取得後、`jq` でフィルターする一連の処理をシェルスクリプトで記載しておけば、叩くだけで取得が行えるので便利です。

なお、例題に挙げた `aws ec2 describe-instances` でも `--filters` `--query` を使用することにより `jq` のようなことは可能です。
うまく組み合わせれば、各々の絞り込みが簡潔になりそうですね。