LoginSignup
92
67

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-24

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

はじめに

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

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

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

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

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

よういするもの

  • 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オブジェクトがあって、

[
  {
    "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的に整形できます。

$ 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].

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

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

実際に抽出

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

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

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

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

そこにTagsKeyNameValueがExactlyapp.commonlispの要素だけ真になる条件を加えます(ルシのファルシがパージでコクーン感ある)。

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

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

$ 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って名前をつけたような気がするけどなー、まったく思い出せない。俺たちは雰囲気でサーバを立てている。そんなアナタに捧ぐ。主にぼく自身に捧ぐ :angel:

$ 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はチューリング完全でもあるらしく、まさしくJSON時代のawk感があります。


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

92
67
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
67