jqコマンドで配列の要素かつ複雑な構造をもつ要素(ここではaws ec2 describe-instancesの結果)に対して検索を行ってみました。
はじめに
株式会社アイリッジのCommon Lisp大好きサーバサイドエンジニア、tanaka.lispです。
AWS EC2を利用していると、特定のインスタンスの情報(インスタンスIDやインスタンスタイプ、などなど…)を知りたくなることがあります。ただ、そのためだけに管理コンソールをぽちぽちして取得していると、なんだか負けた気がするし(これ大事)、シェルスクリプトなどで結果を利用するときに不便です。
awscliのaws ec2 describe-instancesで取得した結果をjqに食わせ、インスタンスにつけている名前からイイかんじに検索・整形したいところですが、awscliはけっこう複雑な返り値を返してきますよね…:
{
"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オブジェクトがあって、
[
{
"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"
}
}
{
... # ずらずら出るので省略
そこにTagsのKeyのNameのValueが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って名前をつけたような気がするけどなー、まったく思い出せない。俺たちは雰囲気でサーバを立てている。そんなアナタに捧ぐ。主にぼく自身に捧ぐ
。
$ 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で検索するだけなのになぜ記事が長くなるのか ![]()