【初心者向け】AWS CLIで分かりやすく出力するために知っておきたいこと




## はじめに
CLIでAWSリソースの設定値を取得したはいいものの要らない情報が多い・・・

ということで今回は、絞り込みの方法をまとめてみました。
長いJSONデータを一つの設定値まで短くして出力します。

#### 内容：
![black1_v8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843865/cab6ae4d-8a8b-84f6-357e-d6ff62536247.png)



## 対象
- AWS CLIを使ったことがない、あまり詳しくない方
- コンソールからパラメータを確認するのが億劫な方
- CLIの結果を分かりやすい形で出力したい方



## 目次
- [CLIによるAWSリソースの情報の取得](#cliによるawsリソースの情報の取得)
- [出力を絞り込む4つの方法](#出力を絞り込む4つの方法)
    - [リソース名やARNでの絞り込み](#-リソース名やarnでの絞り込み)
    - [--filters オプション](#---filters-オプション)
    - [--query オプション](#---query-オプション)
    - [jq コマンド](#-jq-コマンド)
- [その他](#その他)
    - [json以外の形式での出力](#json以外の形式での出力)
    - [完全一致・部分一致でのクエリ](#完全一致部分一致でのクエリ)
    - [絞り込み条件にシェル変数を使うときの書き方とシェルスクリプト](#絞り込み条件にシェル変数を使うときの書き方とシェルスクリプト)



## CLIによるAWSリソースの情報の取得
AWSリソースの設定を確認したいとき、AWS CLIを使えば簡単に設定値を取得することができます。

設定をまとめて確認したいときなどに何度もページ遷移する必要がなくなり便利ですが、下記のようにまとめて出力されるため必要のない情報が多く読みにくいです。

```
# 例）ENIの設定を取得するCLIコマンド
aws ec2 describe-network-interfaces
```

<details><summary>【実行結果】</summary>

```
$ aws ec2 describe-network-interfaces
{
  "NetworkInterfaces": [
      {
          "Status": "in-use",
          "MacAddress": "xx:xx:xx:xx:xx:xx",
          "SourceDestCheck": true,
          "VpcId": "vpc-xxxxxxxx",
          "Description": "Primary network interface",
          "Association": {
              "PublicIp": "xxx.xxx.xxx.xxx",
              "IpOwnerId": "amazon"
          },
          "NetworkInterfaceId": "eni-xxxxxxxx",
          "PrivateIpAddresses": [
              {
                  "Association": {
                      "PublicIp": "xxx.xxx.xxx.xxx",
                      "IpOwnerId": "amazon"
                  },
                  "Primary": true,
                  "PrivateIpAddress": "xxx.xxx.xxx.xxx"
              }
          ],
          "RequesterManaged": false,
          "Ipv6Addresses": [],
          "AvailabilityZone": "us-east-1d",
          "Attachment": {
              "Status": "attached",
              "DeviceIndex": 0,
              "AttachTime": "2013-11-30T23:35:33.000Z",
              "InstanceId": "i-xxxxxxxxxxxxxx",
              "DeleteOnTermination": true,
              "AttachmentId": "eni-attach-xxxxxx",
              "InstanceOwnerId": "123456789012"
          },
          "Groups": [
              {
                  "GroupName": "default",
                  "GroupId": "sg-xxxxxxx"
              }
          ],
          "SubnetId": "subnet-xxxxxxxx",
          "OwnerId": "123456789012",
          "TagSet": [],
          "PrivateIpAddress": "xxx.xxx.xxx.xxx"
      }
  ]
}
```

</details>

## 出力を絞り込む4つの方法
それでは絞り込みを行う4つの方法を紹介します。
①と②の方法はリソースの絞り込みを、③と④はリソースに加えてパラメータの絞り込みまで行います。

### ① リソース名やARNでの絞り込み
あえて挙げるほどではないですが、`--names`や`--resource-arns`のように取得したいリソースをオプションとして指定できるものがあります。

これを使うことで指定したリソースのみの設定値一覧を取得できます。

:::note info
必須ではなく任意となっている場合はデフォルトで全てのリソース設定を取得するようになっていたり、リソース名やARN以外でも絞り込みができたりなどサービスによって異なるため、リファレンスを読むことが大切です。
:::

```
# 例1）ロードバランサーの名前を指定して、設定を取得するCLI
aws elbv2 describe-load-balancers --names xxxxxxxxxxxxx

# 例2）ターゲットグループのARNを指定して、設定を取得するCLI　
aws elbv2 describe-target-groups \
    --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxxx:targetgroup/TG-TEST/xxxxxxxxxxxxx
```

<details><summary>【実行結果 例1】</summary>

```
$ aws elbv2 describe-load-balancers --names ALB-test
{
    "LoadBalancers": [
        {
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:loadbalancer/app/ALB-test/xxxxxxxxxxxx",
            "DNSName": "internal-ALB-test-xxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com",
            "CanonicalHostedZoneId": "xxxxxxxxxxxx",
            "CreatedTime": "2024-12-01T08:54:03.089000+00:00",
            "LoadBalancerName": "ALB-test",
            "Scheme": "internal",
            "VpcId": "vpc-xxxxxxxxxxxx",
            "State": {
                "Code": "active"
            },
            "Type": "application",
            "AvailabilityZones": [
                {
                    "ZoneName": "ap-northeast-1a",
                    "SubnetId": "subnet-xxxxxxxxxxxx",
                    "LoadBalancerAddresses": []
                },
                {
                    "ZoneName": "ap-northeast-1c",
                    "SubnetId": "subnet-xxxxxxxxxxxx",
                    "LoadBalancerAddresses": []
                }
            ],
            "SecurityGroups": [
                "sg-xxxxxxxxxxxx"
            ],
            "IpAddressType": "ipv4",
            "EnablePrefixForIpv6SourceNat": "off"
        }
    ]
}
```

</details>
<details><summary>【実行結果 例2】</summary>

```
$ aws elbv2 describe-target-groups \
>     --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-test/xxxxxxxxxxxx
{
    "TargetGroups": [
        {
            "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-test/xxxxxxxxxxxx",
            "TargetGroupName": "TG-test",
            "Protocol": "HTTP",
            "Port": 80,
            "VpcId": "vpc-xxxxxxxxxxxx",
            "HealthCheckProtocol": "HTTP",
            "HealthCheckPort": "traffic-port",
            "HealthCheckEnabled": true,
            "HealthCheckIntervalSeconds": 30,
            "HealthCheckTimeoutSeconds": 5,
            "HealthyThresholdCount": 5,
            "UnhealthyThresholdCount": 2,
            "HealthCheckPath": "/",
            "Matcher": {
                "HttpCode": "200"
            },
            "LoadBalancerArns": [
                "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:loadbalancer/app/ALB-test/xxxxxxxxxxxx"
            ],
            "TargetType": "instance",
            "ProtocolVersion": "HTTP1",
            "IpAddressType": "ipv4"
        }
    ]
}
```

</details>


### ② --filters オプション
`--filters`は複数の条件が使える高機能版の①のようなものです。
①と同様にサービスごとに使用可否や、絞り込み条件も異なるためリファレンスでの確認が必要です。

また、`--filters`は後述の`--query`とは違ってサーバー側で処理が行われているため高速で、かつデータ転送量の削減ができるという特徴もあるようです。

https://docs.aws.amazon.com/ja_jp/cli/v1/userguide/cli-usage-filter.html

```
# 例1）インスタンスタイプが"t2.micro"のEC2インスタンスの設定を取得するCLI
aws ec2 describe-instances \
    --filters Name=instance-type,Values=t2.micro

# 例2）アベイラビリティゾーンが"ap-northeast-1a"で、停止しているEC2の設定を取得するCLI
aws ec2 describe-instances \
    --filters Name=instance-state-name,Values=stopped,Name=availability-zone,Values=ap-northeast-1a
```

<details><summary>【実行結果 例1&例2】</summary>

```
$ aws ec2 describe-instances \
>     --filters Name=instance-type,Values=t2.micro
     (--filters Name=instance-state-name,Values=stopped,Name=availability-zone,Values=ap-northeast-1a)
{
    "Reservations": [
        {
            "ReservationId": "r-xxxxxxxxxxxxxxxx",
            "OwnerId": "xxxxxxxxxxxxxxxx",
            "Groups": [],
            "Instances": [
                {
                    "Architecture": "x86_64",
                    "BlockDeviceMappings": [
                        {
                            "DeviceName": "/dev/xvda",
                            "Ebs": {
                                "AttachTime": "2024-12-07T15:00:27+00:00",
                                "DeleteOnTermination": true,
                                "Status": "attached",
                                "VolumeId": "vol-xxxxxxxxxxxxxxxx"
                            }
                        }
                    ],
                    "ClientToken": "xxxxxx-xxxx-xxxx-xxxx-xxxxxx",
                    "EbsOptimized": false,
                    "EnaSupport": true,
                    "Hypervisor": "xen",
                    "NetworkInterfaces": [
                        {
                            "Attachment": {
                                "AttachTime": "2024-12-07T15:00:26+00:00",
                                "AttachmentId": "eni-attach-xxxxxxxxxxxxxxxx",
                                "DeleteOnTermination": true,
                                "DeviceIndex": 0,
                                "Status": "attached",
                                "NetworkCardIndex": 0
                            },
                            "Description": "",
                            "Groups": [
                                {
                                    "GroupId": "sg-xxxxxxxxxxxxxxxx",
                                    "GroupName": "SG-TEST"
                                }
                            ],
                            "Ipv6Addresses": [],
                            "MacAddress": "xx:xx:xx:xx:xx:xx",
                            "NetworkInterfaceId": "eni-xxxxxxxxxxxxxxxx",
                            "OwnerId": "xxxxxxxxxxxxxxxx",
                            "PrivateDnsName": "ip-xx-xx-xx-xx.ap-northeast-1.compute.internal",
                            "PrivateIpAddress": "xx.xx.xx.xx",
                            "PrivateIpAddresses": [
                                {
                                    "Primary": true,
                                    "PrivateDnsName": "ip-xx-xx-xx-xx.ap-northeast-1.compute.internal",
                                    "PrivateIpAddress": "xx.xx.xx.xx"
                                }
                            ],
                            "SourceDestCheck": true,
                            "Status": "in-use",
                            "SubnetId": "subnet-xxxxxxxxxxxxxxxx",
                            "VpcId": "vpc-xxxxxxxxxxxxxxxx",
                            "InterfaceType": "interface"
                        }
                    ],
                    "RootDeviceName": "/dev/xvda",
                    "RootDeviceType": "ebs",
                    "SecurityGroups": [
                        {
                            "GroupId": "sg-xxxxxxxxxxxxxxxx",
                            "GroupName": "SG-TEST"
                        }
                    ],
                    "SourceDestCheck": true,
                    "StateReason": {
                        "Code": "Client.UserInitiatedShutdown",
                        "Message": "Client.UserInitiatedShutdown: User initiated shutdown"
                    },
                    "Tags": [
                        {
                            "Key": "Name",
                            "Value": "EC2-TEST"
                        }
                    ],
                    "VirtualizationType": "hvm",
                    "CpuOptions": {
                        "CoreCount": 1,
                        "ThreadsPerCore": 1
                    },
                    "CapacityReservationSpecification": {
                        "CapacityReservationPreference": "open"
                    },
                    "HibernationOptions": {
                        "Configured": false
                    },
                    "MetadataOptions": {
                        "State": "applied",
                        "HttpTokens": "required",
                        "HttpPutResponseHopLimit": 2,
                        "HttpEndpoint": "enabled",
                        "HttpProtocolIpv6": "disabled",
                        "InstanceMetadataTags": "disabled"
                    },
                    "EnclaveOptions": {
                        "Enabled": false
                    },
                    "BootMode": "uefi-preferred",
                    "PlatformDetails": "Linux/UNIX",
                    "UsageOperation": "RunInstances",
                    "UsageOperationUpdateTime": "2024-12-07T15:00:26+00:00",
                    "PrivateDnsNameOptions": {
                        "HostnameType": "ip-name",
                        "EnableResourceNameDnsARecord": false,
                        "EnableResourceNameDnsAAAARecord": false
                    },
                    "MaintenanceOptions": {
                        "AutoRecovery": "default"
                    },
                    "CurrentInstanceBootMode": "legacy-bios",
                    "InstanceId": "i-xxxxxxxxxxxxxxxx",
                    "ImageId": "ami-xxxxxxxxxxxxxxxx",
                    "State": {
                        "Code": 80,
                        "Name": "stopped"
                    },
                    "PrivateDnsName": "ip-xx-xx-xx-xx.ap-northeast-1.compute.internal",
                    "PublicDnsName": "",
                    "StateTransitionReason": "User initiated (2024-12-07 15:00:38 GMT)",
                    "AmiLaunchIndex": 0,
                    "ProductCodes": [],
                    "InstanceType": "t2.micro",
                    "LaunchTime": "2024-12-07T15:00:26+00:00",
                    "Placement": {
                        "GroupName": "",
                        "Tenancy": "default",
                        "AvailabilityZone": "ap-northeast-1a"
                    },
                    "Monitoring": {
                        "State": "disabled"
                    },
                    "SubnetId": "subnet-02bb3xxxxxxxxxxxxxxxxe0267868c851",
                    "VpcId": "vpc-xxxxxxxxxxxxxxxx",
                    "PrivateIpAddress": "xx.xx.xx.xx"
                }
            ]
        }
    ]
}
```

</details>

### ③ --query オプション
`--query`では①や②のようなリソースごとの絞り込みだけでなく、パラメータ単位での絞り込みや出力の整形ができます。

また、クエリの文法はJMESPathというものに準拠しており、詳細は以下のチュートリアルで確認することができます。

https://jmespath.org/tutorial.html#list-and-slice-projections

書き方の基本としては、①や②などで確認できるJSONの出力形式に合わせてキー名を`.`で繋げることで、ネストされたオブジェクトの値を取得することができます。
その際、`{}`ではなく`[]`で囲まれているものは配列であり、キー名の後に`[]`を付けてインデックスを指定する必要があります。

| インデックス式 | 選択対象 |
|:-----------|------------:|
| `[]`| 全ての要素|
| `[0]`| 最初の要素|
| `[1]`| 2番目の要素|
| `[-1]`| 最後の要素|

```
# 例）EC2インスタンスのAMI IDのみを出力するCLI
aws ec2 describe-instances --filters Name=instance-type,Values=t2.micro \
    --query 'Reservations[].Instances[].ImageId'

# 例）ターゲットグループのポート番号のみを出力するCLI
$ aws elbv2 describe-target-groups \
    --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-test/xxxxxxxxxxxx \
    --query 'TargetGroups[].Port'
```

<details><summary>【実行結果】</summary>

```
$ aws ec2 describe-instances --filters Name=instance-type,Values=t2.micro \
>     --query 'Reservations[].Instances[].ImageId'
[
    "ami-xxxxxxxxxxxx",
    "ami-xxxxxxxxxxxx"
]

$ aws elbv2 describe-target-groups \
>     --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-test/xxxxxxxxxxxx \
>     --query 'TargetGroups[].Port'
[
    80
]
```

</details>

柔軟で自由度が高いですが、同じリソースから複数の設定値のみを取得したい場合などにはその度にAPIが叩かれてしまうためやや不向きです。そのような場合には次のjqコマンドを使用します。


### ④ jq コマンド
最後にjqについて、こちらはjsonを整形するためのツールです。
CLIコマンドの出力JSONをシェル変数やファイルなどに保存し、それに対してjqを使って絞り込みを行うといった使い方ができます。

書き方は`--query`と似ていますが、違いとして初めのキー名の前に`.`を付けて始める必要があることに注意が必要です。

```
# 例）ターゲットグループのプロトコルとポート番号のみを出力するコマンド

# CLI結果をシェル変数に格納（1行目）し、プロトコルとポート番号を出力する（2、3行目）
tg_info=$(aws elbv2 describe-target-groups --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST/xxxxxxxxxxxx) &&
echo $tg_info | jq '.TargetGroups[].Protocol' &&
echo $tg_info | jq '.TargetGroups[].Port'
```

<details><summary>【実行結果】</summary>

```
$ tg_info=$(aws elbv2 describe-target-groups --target-group-arns arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST/xxxxxxxxxxxx) &&
> echo $tg_info | jq '.TargetGroups[].Protocol' &&
> echo $tg_info | jq '.TargetGroups[].Port'
"HTTP"
80
```

</details>

jqは①～③と違って外部のツールですが、AWS CloudShellにもプリインストールされていてすぐに使うことができます。
複数回のクエリが一度のCLIコマンドで済むという点が`--query`との使い分けとなる点だと思います。


## その他

### json以外の形式での出力
CLIの出力はデフォルトでjson形式ですが、括弧やダブルクオーテーションが無い文字列の形式で出力することもできます。

CLIオプションでは`--output text`、jqでは`-r`を付けるだけで文字列形式にできます。見やすいだけでなくExcelに張り付けやすいなど使い勝手も良くなります。


### 完全一致・部分一致でのクエリ
`--filter`のようにリソース単位での絞り込みを`--query`やjqコマンドで行いたいときに、完全一致や部分一致を使ったクエリを行います。

なお、下記のCLIの例はこのような3つのターゲットグループが存在する状態での実行です。
| ターゲットグループ名 | プロトコル |
|:-----------|------------:|
| SG-TEST| HTTP|
| SG-TEST2| HTTPS|
| SG-TETT| HTTP|

#### --query での書き方
```
# 例）プロトコルがHTTPのターゲットグループの名前を取得するCLI（完全一致）
aws elbv2 describe-target-groups \
--query "TargetGroups[?Protocol==\`HTTP\`].TargetGroupName" \
--output text

# 例）名前に "-TEST" が含まれるターゲットグループのARNを取得するCLI（部分一致）
aws elbv2 describe-target-groups \
--query "TargetGroups[?contains(TargetGroupName,\`-TEST\`)].TargetGroupArn" \
--output text
```

<details><summary>【実行結果】</summary>

```
$ aws elbv2 describe-target-groups \
> --query "TargetGroups[?Protocol==\`HTTP\`].TargetGroupName" \
> --output text
TG-TEST TG-TETT

$ aws elbv2 describe-target-groups \
> --query "TargetGroups[?contains(TargetGroupName,\`-TEST\`)].TargetGroupArn" \
> --output text
arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST/xxxxxxxxxxxx   arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST2/xxxxxxxxxxxx
```

</details>

注意点として、値が整数である場合（ポート番号など）は`to_string( )`を使って文字列に変換する必要があります。
```
# ポート番号が80のターゲットグループの名前を取得するCLI
tg_port=80
aws elbv2 describe-target-groups \
    --query "TargetGroups[?to_string(Port)=='$tg_port'].TargetGroupName"
```

<details><summary>【実行結果】</summary>

```
$ aws elbv2 describe-target-groups \
>     --query "TargetGroups[?to_string(Port)=='$tg_port'].TargetGroupName" \
>     --output text
TG-TEST TG-TETT
```

</details>

#### jq での書き方
```
# 例）
# 2行目：プロトコルがHTTPのターゲットグループの名前を出力する（完全一致）
# 3行目：名前に "-TEST" が含まれるターゲットグループのARNを出力する（部分一致）
tg_info=$(aws elbv2 describe-target-groups) &&
echo $tg_info | jq -r '.TargetGroups[] | select(.Protocol=="HTTP") | .TargetGroupName'  &&
echo $tg_info | jq -r '.TargetGroups[] | select(.TargetGroupName | contains("-TEST")) | .TargetGroupArn'
```

<details><summary>【実行結果】</summary>

```
$ tg_info=$(aws elbv2 describe-target-groups --region ap-northeast-1) &&
> echo $tg_info | jq -r '.TargetGroups[] | select(.Protocol=="HTTP") | .TargetGroupName'  &&
> echo $tg_info | jq -r '.TargetGroups[] | select(.TargetGroupName | contains("-TEST")) | .TargetGroupArn'
TG-TEST
TG-TETT
arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST/xxxxxxxxxxxx
arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/TG-TEST2/xxxxxxxxxxxx
```

</details>

### 絞り込み条件にシェル変数を使うときの書き方とシェルスクリプト
複数のCLIコマンドをシェルスクリプトにすることでまとめて実行することもできます。連番で管理されたAWSリソースの情報を取得したいときなどに使えました。

やりたいことは絞り込みの条件にシェル変数を使い、for文などでリソース分のループをさせるだけなのですが、変数の囲み方の種類が多く覚えにくい（覚えられそうにない）ので整理しておこうと思います・・・

特に「''（シングルクォート）」と「``（バッククォート）」の違いに注意が必要です。

#### １．シェル変数にCLI結果を格納する
`$( )`でCLIコマンドを囲むことでシェル変数にCLI結果を格納できます。
```
# CLIの結果を入れる
ec2_json=$(aws ec2 describe-instances --instance-ids i-xxxxxxxx)
```

#### ２．CLIオプションにシェル変数を使う
`${ }`でシェル変数を囲むことで、CLIコマンドにシェル変数を混ぜることができます。
`--query`の中で使う場合は、シングルクォートで囲む必要があります。
```
# CLIオプションにシェル変数を使う
instance_id="i-xxxxxxxxxxxxxx" # インスタンスIDのシェル変数

aws ec2 describe-instances \
    --instance-ids ${instance_id}

# --queryにシェル変数を使う（部分一致の場合も同様）
tg_protocol=HTTP # ターゲットグループのプロトコルのシェル変数

aws elbv2 describe-target-groups \
    --query "TargetGroups[?Protocol=='$tg_protocol'].TargetGroupName"
```

#### ３．jqコマンドにシェル変数を使う
jqコマンドで完全一致や部分一致の条件にシェル変数を使う場合は、`"'${ }'"`で囲む必要があります。
```
# jqコマンドにシェル変数を使う（部分一致の場合も同様）
tg_protocol="HTTP" # ターゲットグループのプロトコルのシェル変数
tg_info=$(aws elbv2 describe-target-groups) # ターゲットグループの設定値JSON

echo $tg_info | jq -r '.TargetGroups[] | select(.Protocol=="'${tg_protocol}'") | .TargetGroupName'
```


## おわりに
以上、CLIに初めて触れてから絞り込みを行えるようになるまでに学んだ内容をまとめてみました。
大量のリソースの設定を確認する場合でないとあまり活きないものだと思いますが、色々と勉強になり良かったです。