個人的な jq あんちょこ兼メモです。
目次:
- 値が特定の文字列にマッチするデータを抽出する
- キーリストを取得
-
selectで出力対象を絞る - key - value 構造の key
- オブジェクトリストからキーとその出現数を抽出する
- オブジェクトリストから一つのキーバリューを取り出す
- オブジェクトリストの値から新たなオブジェクトを作る
- 複数の JSON ファイルを一つのリストに格納する -
-sオプション - AWS EC2 のタグ情報からパブリック DNS 名を取得 - AWS CLI
- 特定日以降で Name タグのあるスナップショットのリストを得る - AWS CLI
値が特定の文字列にマッチするデータを抽出する
$ json='[{"genre":"deep house"}, {"genre": "progressive house"}, {"genre": "dubstep"}]'
$ echo "$json" | jq -c '.[] | select(.genre | contains("house"))'
{"genre":"deep house"}
{"genre":"progressive house"}
参考: “jq match value Code Example”
キーリストを取得
$ cat hash.json | jq 'keys'
ハッシュリストの場合や、それらの出現数等など応用方法はオブジェクトリストからキーとその出現数を抽出するを参照
select で出力対象を絞る
例 : リストから mail が空じゃないレコードだけ処理する。
$ cat hoge.json | jq -r '.[] | select( .email != "" ) | [ .name , .email ] | @tsv'
データによっては select 部分は select( .email != null ) の方が良い結果になるかもしれない。
key - value 構造の key
ユニークであることが自明なリストとして連想配列を使う場合以下の様なしばしばデータ構造になる。
{
"foo" : {
"url" : "www.foo.com",
"email" : "foo@foo.com"
},
"bar" : {
"url" : "www.bar.com",
"email" : "bar@bar.com"
}
...
}
こういった場合 jq で jq '.[]' で バリュー(.url,.email)を舐めるのは簡単だがその場合に key が取り出せない。
このような場合、組み込み関数の to_entries で[{"key" : "foo" ,"value" : {..}},...] という形に一旦トランスフォームしてから処理すれば良い。
$ cat user_sites.json | jq -r '. | to_entries | .[] | [ .key , .value.email ] | @csv'
"foo","foo@foo.com"
"bar","bar@bar.com"
オブジェクトリストからキーとその出現数を抽出する
$ jq -r '[.[]|keys]|flatten|group_by(.) | map({(.[0]): length}) | add'
例 :
[
{
"x" : "hoge",
"y" : "moge",
"z" : "zoge"
},
{
"x" : "ほげ",
"z" : "ぞげ"
}
]
から
{
"x": 2,
"y": 1,
"z": 2
}
というオブジェクトを取得する
キーのユニークリストを取得するだけなら
$ jq -r '[.[]|keys]|flatten|unique|sort'
で OK
解説(出現数まで出すケース)
1. keys でキーで構成されるリストを取得する
$ echo '[{"x":"hoge","y":"moge","z":"zoge"},{"x":"ほげ","z":"ぞげ"}]' | jq -r '.[]|keys'
[
"x",
"y",
"z"
]
[
"x",
"z"
]
2. 上記コマンドを[] でくくることで JSON として再処理可能にする
$ echo '[{"x":"hoge","y":"moge","z":"zoge"},{"x":"ほげ","z":"ぞげ"}]' | jq -r '[.[]|keys]'
[
[
"x",
"y",
"z"
],
[
"x",
"z"
]
]
3. flattenで一次配列化する
echo '[["x","y","z"],["x","z"]]' | jq -r '. | flatten'
[
"x",
"y",
"z",
"x",
"z"
]
4. group_by でグルーピングする
$ echo '["x","y","z","x","z"]' | jq '. | group_by(.)'
[
[
"x",
"x"
],
[
"y"
],
[
"z",
"z"
]
]
5. map で各リスト毎にその 1 アイテム目を key にして配列長を value に変形する
$ echo '[["x","x"],["y"],["z","z"]]' | jq '. | map({(.[0]):length})'
[
{
"x": 2
},
{
"y": 1
},
{
"z": 2
}
]
6. add で一つのオブジェクトにまとめる
$ echo '[{"x":2},{"y":1},{"z":2}]' | jq '. | add'
{
"x": 2,
"y": 1,
"z": 2
}
オブジェクトリストから一つのキーバリューを取り出す
やりたいこと
{
"family_name": "foo-bar",
"family":
[
{
"name": "hoge",
"age": 10
},
{
"name": "moge",
"age": 11
}
]
}
という感じのデータからトップレベル family_name の値と、 moge さんの年齢を一発で取り出したい
解答
$ echo '{"family_name": "foo-bar","family":[{"name": "hoge","age": 10},{"name": "moge","age": 11}]}' | jq '[ .family_name , (.family[] | select( .name == "moge" ) | .age) ]'
[
"foo-bar",
11
]
注意事項
jq の引数にはシングルクオートを使う
jq に渡す文字列をダブルクオートで渡すと、bash の [(test) や select などが解釈されてしまうため
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at <top-level>, line 1:
.family[] | select( .name == 'moge' ) | .age
jq: 1 compile error
という感じのエラーになるので注意。
オブジェクトリストの値から新たなオブジェクトを作る
に似ているが、やりたいことは:
[
{
name: "hoge",
age: 10
},
{
name: "moge",
age: 11
}
]
という感じのオブジェクトリストから
{
"hoge": 10
"moge": 11
}
という感じのハッシュを作る。
ちょっと分からなかったので google 先生に聞いて見つけた。
$ jq 'map( {(.name): .age} ) | add'
いまいち map と add の挙動が分からない。
いや map はなんとなく分かるが、その出力と add の作用が良く分からないと行った方が正確か。
map は perl 同様、リストの要素に対しで処理を行う。
map_values を使うと、ハッシュの値に対して処理を行うことが出来る。
今回の場合、リストの要素であるオブジェクトを {(.name): .age} に置き換えてると読むことが出来るが、その後の add が分からない。
実際に add なしで実行した結果は以下の通りだ。
$ jq 'map( {(.name): .age} )' data.json
[
{
"hoge": 10
},
{
"moge": 11
}
]
公式の add のサンプルは以下の様なものである
jq 'add'
Input ["a","b","c"]
Output "abc"
リファレンスチョー訳抜粋
入力配列の要素のタイプに応じて、足し算や文字列結合、マージを意味する。
となると、わざわざ map を使わなくても出来るはず
jq '[.[]|{(.name): .age}] | add' data.json
{
"hoge": 10,
"moge": 11
}
できた
複数の JSON ファイルを一つのリストに格納する - -s オプション
-s,--slurp
入力のJSONオブジェクトごとにフィルターを実行するのではなく、入力ストリーム全体を大きな配列に読み込んで、一度だけフィルターを実行します。
$ jq -s '.' file1 file2 ...
AWS CloudTrail のようにファイル名に日時が付与された JSON ファイル群を join したいなら ls + sort 等を利用する。
$ jq -s '.' $(ls *.json | sort)
$ jq -s '[.[]|keys]|flatten|unique' $(find extracted-json -name "*.json" -not -name "invalid-*")
[
"_access_log_attr",
"hardware",
"salt",
"unity",
"user"
]
AWS EC2 のタグ情報からパブリック DNS 名を取得 - AWS CLI
タグ UniqueName の値が Tags value であるインスタンスのパブリック DNS 名を取得する
$ aws ec2 describe-instances --filters '{"Name":"tag:UniqueName","Values": ["Tags value"]}' | jq -r '.Reservations[0].Instances[0].PublicDnsName'
特定日以降で Name タグのあるスナップショットのリストを得る - AWS CLI
aws ec2 describe-snapshots --query "Snapshots[?(StartTime>='2021-03-13')]" | jq -r '.[] | select( .Tags != null ) | select( (.Tags[] | select( .Key == "Name" ))) | [ (.Tags[] | select( .Key == "Name" ) | .Value ) , .SnapshotId , .StartTime , .Description ] | @tsv' | sort
要点解説
-
--query "Snapshots[?(StartTime>='2021-03-13')]"- スナップショットの作成開始時刻 =
StartTimeが 2021-03-13(の 00:00:00)以降のスナップショットを取り出す
- スナップショットの作成開始時刻 =
AWS CLI の返り値は各スナップショットの情報を格納した dict のリスト
-
.[] | select( .Tags != null )- リストからタグが空では無い要素を取り出す(暗黙的にスナップショットが取られているものが多数あるっぽく、それらには
Tagsが無い。このためそれらについてタグ内の要素を参照するとnullのアトリビュートを参照する式になってしまいjqが死ぬのでフィルタリング)
- リストからタグが空では無い要素を取り出す(暗黙的にスナップショットが取られているものが多数あるっぽく、それらには
-
| select( (.Tags[] | select( .Key == "Name" )))- タグのキーが
Nameのアイテムがあるものを取り出す - タグ(
Tags[])がるのでjqが死なない
- タグのキーが
-
| [ (.Tags[] | select( .Key == "Name" ) | .Value ) , .SnapshotId , .StartTime , .Description ]- 欲しいアトリビュートを列挙した配列を作る