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
で検索するだけなのになぜ記事が長くなるのか