細かすぎて伝わらないAWSCLI芸

LL言語のAWS SDKなど甘えだ！（挨拶

AWSオペレータの職を初めてから1年半ほど、何かとAWSCLIで作業（主に現在の設定値の洗い出し等）することが多く、ときにはパッと見で難解なワンライナーが出来上がってしまうため、引き継ぎ＆個人のメモがてら記事にした次第です。

お決まり文句ですが、 __本記事の情報を利用した結果、生じた金銭やセキュリティ被害等の損害について、一切責任を負いません。__ きちんと検証した上でご利用ください。
また各コマンドのオプション等は各自お調べください。

あと、以下の環境にて動作確認をしております。方言的なところは逐次変更してください。

- AWSCLI(1.11.37)
- jq(1.5)
- zsh(5.0.8、ちょっと古いですね)

## その1：多段リクエスト

1回目のAWSCLIの結果を2回目に使いたい・・・という場合はよくあります。
これを実現するには `xargs`コマンドかfor文を使うとできます。
xargsの場合は、必然的に `jq` コマンドを使用することになるかと思います。

例： RDSのパラメータグループのKeyとValueを取得する（describe-db-parameter-groups -> describe-db-parameters）

- xargs & jq

```
$ aws rds describe-db-parameter-groups \
  | jq '.DBParameterGroups[].DBParameterGroupName' -r \
  | xargs -I% aws rds describe-db-parameters --db-parameter-group-name % \
  | jq '.Parameters[]|{Name: .ParameterName, Value: .ParameterValue}'
```

- for文（トップレベルが配列になります）

```
$ for db in $(aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName' --output text);
do
  aws rds describe-db-parameters --db-parameter-group-name ${db} \
    --query 'Parameters[*].{Name:ParameterName,Value:ParameterValue}'
done
```

## その2:その1をしつつ特定文字列を含むものに絞り込みしたい

jqを使う場合はselect、awscliの場合はqueryオプション内でfilterします。

例: `character` という文字列が入ったパラメータ名（例えば`character_set_client` とか）のみを出力したい

- xargs & jq

```
$ aws rds describe-db-parameter-groups \
  | jq '.DBParameterGroups[].DBParameterGroupName' -r \
  | xargs -I% aws rds describe-db-parameters --db-parameter-group-name % \
  | jq '.Parameters[]|select(.ParameterName|contains("character"))|{Name: .ParameterName, Value: .ParameterValue}'
```

- awscli

```
$ for db in $(aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName' --output text);
do
  aws rds describe-db-parameters --db-parameter-group-name ${db} \
    --query 'Parameters[?contains(ParameterName, `character`)].{Name:ParameterName,Value:ParameterValue}'
done
```

## その3：その2をしつつ特定文字列を含むものを除外したい

jqの場合、論理演算子を使う方法とパイプを使ってフィルタする方法があります。状況に応じて変えましょう。
awscliは論理演算子を駆使します。
もし `true` や `false` を使う場合はバッククォートで囲む必要があるので注意しましょう。

例： `handshake` という文字列が入ったものを除外する

- xargs & jq （論理演算子）

```
$ aws rds describe-db-parameter-groups \
  | jq '.DBParameterGroups[].DBParameterGroupName' -r \
  | xargs -I% aws rds describe-db-parameters --db-parameter-group-name % \
  | jq '.Parameters[]|select(.ParameterName|contains("character") and (contains("handshake")|not))|{Name: .ParameterName, Value: .ParameterValue}'
```

- xargs & jq （パイプ）

```
$ aws rds describe-db-parameter-groups \
  | jq '.DBParameterGroups[].DBParameterGroupName' -r \
  | xargs -I% aws rds describe-db-parameters --db-parameter-group-name % \
  | jq '.Parameters[]|select(.ParameterName|contains("character"))|select(.ParameterName|contains("handshake")|not)|{Name: .ParameterName, Value: .ParameterValue}'
```

- awscli

```
$ for db in $(aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName' --output text);
do
  aws rds describe-db-parameters --db-parameter-group-name ${db} \
    --query 'Parameters[?contains(ParameterName, `character`) && !contains(ParameterName, `handshake`)].{Name:ParameterName,Value:ParameterValue}'
done
```

- awscli （false使用）

```
$ for db in $(aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName' --output text);
do
  aws rds describe-db-parameters --db-parameter-group-name ${db} \
    --query 'Parameters[?contains(ParameterName, `character`) && contains(ParameterName, `handshake`) == `false`].{Name:ParameterName,Value:ParameterValue}'
done
```


## その4：その3をしつつトップレベルに識別子キーを入れたい

その1〜3の例だと、どのPGの値かわからない・・・ので、トップレベルにPG名を入れます。
ここからはシェルのパイプラインでPG名を渡せないので、xargsを使わずに awscli & `--query` オプションでがんばります。

また、シェル変数展開する必要があるため、awscliのqueryに渡していた文字列をシングルクォート囲いからダブルクォート囲いにして、文字列内のダブルクォート・バッククォートをエスケープします。

例：トップレベルのキーにdb-parameter-group-nameを入れる。

```
$ for db in $(aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName' --output text);
do
  aws rds describe-db-parameters --db-parameter-group-name ${db} \
    --query "{\"${db}\":Parameters[?contains(ParameterName, \`character\`) && !contains(ParameterName, \`handshake\`)].{Name:ParameterName,Value:ParameterValue}}"
done
```

出力

```json
{
    "aibou-db-parameters": [
        {
            "Name": "character_set_client",
            "Value": "utf8mb4"
        },
        {
            "Name": "character_set_connection",
            "Value": "utf8mb4"
        },
        {
            "Name": "character_set_database",
            "Value": "utf8mb4"
        },
        {
            "Name": "character_set_filesystem",
            "Value": "utf8mb4"
        },
        {
            "Name": "character_set_results",
            "Value": "utf8mb4"
        },
        {
            "Name": "character_set_server",
            "Value": "utf8mb4"
        }
    ],
    # 続く
}
```

## key名を変数展開したい(未解決)

```json
{
  "character_set_client" : "utf8mb4",
  "character_set_connection" : "utf8mb4"
}
```

の様な出力がほしかったものの、awscliでもjqでも方法わからず（コメントや記事リクエスト等で受付させていただきます。）

## その他(awscli alias)

awscliでaliasを作ることができます。 詳しくはこちら： http://dev.classmethod.jp/cloud/aws/aws-cli-alias/

functionを定義できるのが非常に魅力的なのですが、現状引数を（与えられない/取れない）仕様になっているようです。
どうしても引数を使いたい場合は、 下記を試されると良いかなと思います。
（僕はこの方法を見てドン引きしました。）
https://github.com/awslabs/awscli-aliases/issues/19#issuecomment-280524125 

## 終わりに

他に書き方生まれたら ＆ 気が向いたら逐次追加していきます。
コメント等でご提案・ご指摘も受けさせていただきます。

参考記事： http://dev.classmethod.jp/cloud/awscli-enhanced-query-opt-snipets/

（クラメソさんブログのリンクが多めですが、決して回し者ではありません。僕のポッケには1円も入らないです）