jqでちょっぴり複雑な検索をする

　`jq`コマンドで配列の要素かつ複雑な構造をもつ要素(ここでは`aws ec2 describe-instances`の結果)に対して検索を行ってみました。

# はじめに

　株式会社アイリッジのCommon Lisp大好きサーバサイドエンジニア、tanaka.lispです。

　AWS EC2を利用していると、特定のインスタンスの情報(インスタンスIDやインスタンスタイプ、などなど…)を知りたくなることがあります。ただ、そのためだけに管理コンソールをぽちぽちして取得していると、なんだか負けた気がするし（これ大事）、シェルスクリプトなどで結果を利用するときに不便です。

　`awscli`の`aws ec2 describe-instances`で取得した結果を`jq`に食わせ、インスタンスにつけている名前からイイかんじに検索・整形したいところですが、`awscli`はけっこう複雑な返り値を返してきますよね…:

```json
{
  "Reservations": [
    "Instances": [
      {
        "InstanceId": "i-commonlisp",
        "Tags": [
          {
            "Key": "Name",
            "Value": "app.commonlisp"
          },
          {
            "Key": "aws.cloudformation:stack-name",
            "Value": "stack.commonlisp"
          }
        ],
        ...
      },
      ...
    ],
    ...
  ]
}
```

　そこで、名前(`Tags`の`Key`が`Name`の値)によるインスタンスの検索方法を試行錯誤しました。

# よういするもの

- `jq`: `jq-1.5-1-a5b5cbe`
- `awscli`: `aws-cli/1.11.58 Python/3.6.0 Linux/4.8.0-41-generic botocore/1.5.21`

# jqでの検索

　`jq`では入力のJSONに対して、以下のようなことができます:

- 入力JSONの整形 (`{"id": .InstanceId, "tags": .Tags}`)
- 入力JSONからのデータ抽出 (`select`)

　今回はこれらを組み合わせて、`aws ec2 describe-instances`の結果から探しているインスタンスのインスタンスID(`InstanceId`)を取得してみます。

## 入力の整形

　たとえばこんなJSONオブジェクトがあって、

```json
[
  {
    "name": "Lisp",
    "developer": "John McCarthy"
  },
  {
    "name": "Common Lisp",
    "developer": "ANSI X3J13 committee",
    "spec": "ANSI INCITS 226-1994 (R2004)"
  },
  {
    "name": "Arc",
    "developer": "Paul Graham",
    "books": [
      "On Lisp",
      "Hackers and Painters"
    ]
  },
  {
    "name": "Clojure",
    "developer": "Rich Hicky"
  }
]
```

言語名(`lang`)と開発者名(`developer`)だけがほしいとき、入力を`printf`的に整形できます。

```json
$ echo '...上のJSON...' \
   | jq '.[] | {"lang": .name, "developer": .developer}'
{
  "lang": "Lisp",
  "developer": "John McCarthy"
}
{
  "lang": "Common Lisp",
  "developer": "ANSI X3J13 committee"
}
{
  "lang": "Arc",
  "developer": "Paul Graham"
}
{
  "lang": "Clojure",
  "developer": "Rich Hicky"
}
```

要素の抽出のようにも思えますが、別にもっと抽出っぽいオペレータがあるので、こちらは`整形`と呼びました。

## 入力からのデータ抽出

　条件を満たすもののみをpass throughするというオペレータもあって、それが`select`です。こちらを`抽出`とここでは呼んでいます。

　`man`にあることがすべてなのですが、

```
   select(boolean_expression)
       The function select(foo) produces its input unchanged if foo returns true for that input, and produces no output otherwise.

       It´s useful for filtering lists: [1,2,3] | map(select(. >= 2)) will give you [2,3].
```

正規表現を組み合わせたりできるので強力です;

```json
$ echo '...上のJSON...' \
  | jq '.[] | .name |select( .| test("Lisp"))'
"Lisp"
"Common Lisp"
```

# 実際に抽出

## Exactlyな名前でインスタンスの検索

　では実際に検索クエリをつくってみます。

　まずはインスタンスリストを得て、

```sh
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]'
{
  "ImageId": "ami-commonlisp",
  "State": {
    "Code": 16,
    "Name": "running"
  }
}
{
...  # ずらずら出るので省略
```

そこに`Tags`の`Key`の`Name`の`Value`がExactly`app.commonlisp`の要素だけ真になる条件を加えます（[ルシのファルシがパージでコクーン](http://dic.nicovideo.jp/a/%E3%83%91%E3%83%AB%E3%82%B9%E3%81%AE%E3%83%95%E3%82%A1%E3%83%AB%E3%82%B7%E3%81%AE%E3%83%AB%E3%82%B7%E3%81%8C%E3%83%91%E3%83%BC%E3%82%B8%E3%81%A7%E3%82%B3%E3%82%AF%E3%83%BC%E3%83%B3)感ある）。

```sh
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
{
  "InstanceId": "i-commonlisp",
  ...  # ずらずら出るので省略
}
```

　ついでに、不要な要素は出ないようにしましょう。

```sh
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
```

## 名前うろおぼえインスタンスの検索

　なんか`app`って名前をつけたような気がするけどなー、まったく思い出せない。[俺たちは雰囲気でサーバを立てている](http://orekabu.jp/bakakabu/)。そんなアナタに捧ぐ。主にぼく自身に捧ぐ :angel: 。

```sh
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select( .Tags[].Key == "Name" and (.Tags[].Value | test("^app")))
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
{
  "instance-id": "i-clojure",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.clojure"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "clojure-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
```


# おわりに

　`jq`での検索について、以下のことを述べました:

- 要素を検索する基本的な方法
- 対象要素のさらに中の辞書も検索条件にする方法

　`jq`ってよくできたDSLですね。[`jq`はチューリング完全](https://github.com/MakeNowJust/bf.jq
)でもあるらしく、まさしくJSON時代の`awk`感があります。

------

　ところで`jq`で検索するだけなのになぜ記事が長くなるのか :sob: