jqはJSONを「構造」として扱い、必要な値だけを抜き出す専用ツールです。この記事はAWS CLIの出力・APIレスポンス・JSON Linesログを題材に、「やりたいこと → jqフィルタ」をコピペで試せる逆引きリファレンス。未経験の方でも順に手を動かせるよう、最後に1画面の早見表を用意しました。
01なぜjqなのか — CLIもAPIもログも、全部JSON
クラウド運用では、AWS CLIの出力・REST APIのレスポンス・構造化ログ・設定ファイルまで、あらゆるものがJSONでやり取りされます。ところがJSONは階層が深く、目で追うのもgrepで抜くのも限界があります。jqはJSONを「構造」として理解し、必要な値だけを取り出す専用ツールです。目視やgrepではなく、jqで確実に抜くのが基本姿勢です。
- AWS CLIの巨大な describe-* 出力から、欲しいID・IPだけを抜く
- APIレスポンスを整形して確認し、そのまま次のコマンドへ渡す
- 1行1JSON(JSON Lines)のログを、条件で絞り込む
--query(JMESPath)が内蔵されています。追加インストール不要で、サーバに近い段階で絞れる(=転送量を減らせる)のが利点。一方 jq は AWS 以外のJSONにも同じ書き味で使えます。まず --query で粗く絞り、複雑な整形・集計は jq に渡す——この使い分けが実務的です。AWS CLI側の詳細はAWS CLI 実践リファレンスにまとめています。brew install jq(macOS)/ sudo dnf install -y jq(Amazon Linux 2023)/ sudo apt-get install -y jq(Ubuntu)。JSONやコマンドラインの土台から固めたい方はクラウドエンジニアの基礎(TCP/IP教科書)もあわせてどうぞ。02基本 — まず jq . で全体を見て、1段ずつ足す
jqの式(フィルタ)は、左から右へJSONを流していく発想です。まず jq . で全体を整形して眺め、.Key を1段ずつ足していくのが最短ルートです。
# そのまま整形して全体を見る(pretty print)
echo '{"name":"web","tags":["a","b"]}' | jq .
# キーを指定して値を取り出す
echo '{"name":"web"}' | jq '.name' # => "web"(ダブルクオート付き)
echo '{"name":"web"}' | jq -r '.name' # => web(-r で生文字列)
# ネストは . でつなぐ
echo '{"a":{"b":1}}' | jq '.a.b' # => 1
# 配列を展開 / 添字でアクセス
echo '[10,20,30]' | jq '.[]' # 10 / 20 / 30 を各行に
echo '[10,20,30]' | jq '.[0]' # => 10
# パイプ | で段階的に(.users を展開して .name を取る)
echo '{"users":[{"name":"a"},{"name":"b"}]}' | jq -r '.users[].name' -r(--raw-output): 文字列のダブルクオートを外して生で出す。シェル変数や次コマンドへ渡すとき必須。-c(--compact-output): 1行に詰めて出す。ログや配列を1件1行にしたいとき。|(パイプ): jqフィルタの中でも使う。.a | .bのように、Unixのパイプと同じ発想でつなぐ。
jq . で全体を見てから .Key を1段ずつ足す。エラーが出たら1段戻す。これが遠回りに見えて一番速い進め方です。03ユースケース:AWS CLIの出力を絞る
aws ec2 describe-instances の出力は Reservations の中に Instances が入る二重ネストです。ここから InstanceId だけを抜きます。
# 実行中インスタンスのIDを一覧(-r で生文字列 → 次のコマンドに渡せる)
aws ec2 describe-instances \
| jq -r '.Reservations[].Instances[].InstanceId'
# Name タグと ID を並べる(タグは配列なので select で Key=="Name" を探す)
aws ec2 describe-instances \
| jq -r '.Reservations[].Instances[]
| [.InstanceId, (.Tags[]? | select(.Key=="Name") | .Value)]
| @tsv' --query でもできます:aws ec2 describe-instances --query "Reservations[].Instances[].InstanceId" --output text。サーバ側で絞れるぶん大きな出力では有利です。jqは整形・集計や、AWS以外のJSONにも使える点で守備範囲が広い——場面で使い分けましょう。04ユースケース:条件で絞る(select / map)
「条件に合うものだけ残す」には select(...)、「配列を配列のまま変換する」には map(...) を使います。
# 各要素を select で条件フィルタ(running のみ)
aws ec2 describe-instances \
| jq '.Reservations[].Instances[] | select(.State.Name=="running")'
# map: 配列を配列のまま変換(各要素に式を適用)
echo '[{"n":1},{"n":2},{"n":3}]' | jq 'map(.n)' # => [1,2,3]
echo '[{"n":1},{"n":2},{"n":3}]' | jq 'map(select(.n>1))' # => [{"n":2},{"n":3}]
# 複数条件(and / or)と文字列一致
jq '.[] | select(.env=="prod" and .cpu>80)'
jq '.[] | select(.name | startswith("web"))'
jq '.[] | select(.name | test("web[0-9]+"))' # 正規表現でマッチ .[] | select(...)は「1件ずつ流して条件で残す」。結果は要素の“列”(配列ではない)。map(select(...))は「配列を配列のまま絞る」。JSONの配列構造を保ちたいときはこちら。- 文字列演算
startswith/endswith/contains/test(正規表現)が使える。
05ユースケース:整形・変換(オブジェクト構築 / @csv / to_entries)
欲しいキーだけの新しいオブジェクトを組み立てたり、CSV/TSVに落としたりします。表計算や別ツールへ渡すときの定番です。
# 欲しいキーだけの新オブジェクトを組み立てる
aws ec2 describe-instances \
| jq '.Reservations[].Instances[]
| {id: .InstanceId, type: .InstanceType, ip: .PrivateIpAddress}'
# CSV / TSV に落とす(配列にしてから @csv / @tsv、-r 必須)
jq -r '.Reservations[].Instances[]
| [.InstanceId, .InstanceType, .PrivateIpAddress] | @csv'
# to_entries: オブジェクトを {key,value} の配列へ(タグ整形などに便利)
echo '{"Env":"prod","App":"web"}' | jq 'to_entries'
# => [{"key":"Env","value":"prod"},{"key":"App","value":"web"}]
# keys: キー名の一覧(ソートされる。順序を保つなら keys_unsorted)
echo '{"b":1,"a":2}' | jq 'keys' # => ["a","b"] @csv は各値をクオートしてカンマ区切りに、@tsv はタブ区切りにします。どちらも入力は配列である必要があり、生の行として得るため -r を付けます。APIレスポンスをこの形で整えてから curl 実践リファレンスのパイプに組み込むと確認が捗ります。06ユースケース:数える・まとめる(length / group_by / add)
件数を数える、種類ごとに集計する、合計・並べ替え・重複除去をする——ログや棚卸しでよく使う操作です。
# 件数を数える(.[] で展開したものは [ ] で配列に包んでから length)
aws ec2 describe-instances | jq '[.Reservations[].Instances[]] | length'
# 状態ごとに件数を集計(group_by → 各グループを {state,count} に)
aws ec2 describe-instances \
| jq '[.Reservations[].Instances[]]
| group_by(.State.Name)
| map({state: .[0].State.Name, count: length})'
# 合計 / 並べ替え / 重複除去 / 最大
echo '[3,1,2]' | jq 'add' # => 6
echo '[3,1,2]' | jq 'sort' # => [1,2,3]
echo '[{"n":3},{"n":1}]' | jq 'sort_by(.n)' # n の昇順に並べ替え
echo '["a","a","b"]' | jq 'unique' # => ["a","b"]
echo '[{"n":3},{"n":9}]' | jq 'max_by(.n)' # => {"n":9} group_by / sort_by / add / length は「配列」に対する操作です。.[] で展開したものは配列ではないので、[ ... ] で一度配列に包んでから渡す。ここのミスが集計でハマる定番です。07ユースケース:JSON Lines ログを調査する
CloudWatch Logs や多くのアプリログは「1行1JSON(JSON Lines)」形式です。jqは各行を独立したJSONとして順に処理するので、そのまま select で絞れます。
# ERRORレベルだけを、1件1行で抜く(-c で1行に詰める)
cat app.log | jq -c 'select(.level=="ERROR")'
# 必要な列だけ、時刻つきで読みやすく(\(...) で文字列補間)
jq -r 'select(.level=="ERROR") | "\(.time) \(.msg)"' app.log
# メッセージに特定の語を含む行("i" で大文字小文字を無視)
jq -c 'select(.msg | test("timeout"; "i"))' app.log
# 出現の多いエラーを数える(-s で全行を1配列にまとめてから集計)
jq -s 'group_by(.msg) | map({msg: .[0].msg, count: length})
| sort_by(-.count)' app.log -c(compact)で1件1行に保つと、grepやwc -lと組み合わせやすい。-s(--slurp)は入力全体を1つの配列に読み込む。全体を集計・ソートしたいときに使う(大きなファイルではメモリに注意)。- 文字列補間
"\(.field)"で、見やすい1行ログに整形できる。
grep / awk / sort など周辺コマンドとの組み合わせは Linux 実践コマンドリファレンスにまとめています。CloudWatch Logs Insights のような専用クエリがある場合はそちらが速いこともあります。手元に落としたログや、パイプ途中の軽い絞り込みなら jq が手軽です。08つまづきどころ — クォート・null安全・-r
jqのハマりどころは、ほぼ「シェルのクォート」と「nullの扱い」に集約されます。ここを押さえれば大半のエラーは自己解決できます。
- フィルタは必ずシングルクオート
'...'で囲む。ダブルクオートだとシェルが$や バッククオートを展開して壊れる。 - フィルタ内で変数を使うなら
--arg name valで渡し、$nameで参照する(文字列として安全にエスケープされる)。 - 無いキーや配列でない値に
.[]を当てるとエラー。.Tags[]?のように?を付けると、無い場合は静かにスキップする(null安全)。 - 値が null のとき既定値を与えるには
//。.name // "unknown"は name が null/未定義なら "unknown"。 - 文字列としてそのまま使う(変数へ代入・次コマンドへ渡す)なら
-rを忘れない。
# NG: ダブルクオートだと $HOME などが展開されて壊れる
jq ".users[].name" data.json # 避ける
# OK: シングルクオート。変数は --arg で安全に渡す
jq -r --arg env prod '.[] | select(.env==$env) | .name' data.json
# null安全 ? と 既定値 // の合わせ技
aws ec2 describe-instances \
| jq -r '.Reservations[].Instances[]
| (.Tags[]? | select(.Key=="Name") | .Value) // "no-name"' "i-123" になる」——これはほぼ -r の付け忘れです。おかしいと思ったら、まず -r を疑いましょう。--arg で渡す値も含め、秘密情報は YOUR_KEY のようなプレースホルダで示し、Gitやスクリプトにコミットしないのが原則です。09逆引き早見表 — やりたいこと → jqフィルタ
迷ったらここから。1画面のチートシートとして手元に置いておくと、日々の運用が速くなります。
| やりたいこと | jqフィルタ(コピペ用) |
|---|---|
| 全体を整形して眺める | jq . |
| キーの値を生文字列で取る | jq -r '.key' |
| 配列を1件ずつ展開 | jq '.[]' |
| ネストした値を取る | jq '.a.b.c' |
| 条件で絞る | jq '.[] | select(.state=="running")' |
| 欲しいキーだけ再構成 | jq '{id:.InstanceId, ip:.PrivateIpAddress}' |
| CSV/TSVに変換 | jq -r '[.a,.b] | @csv' |
| 件数を数える | jq '[.[]] | length' |
| 種類ごとに集計 | jq 'group_by(.k) | map({k:.[0].k, n:length})' |
| 重複除去 / 並べ替え | jq 'unique' / jq 'sort_by(.n)' |
| JSON Linesログを絞る | jq -c 'select(.level=="ERROR")' |
| 無いキーを無視(null安全) | jq '.Tags[]?' |
| nullに既定値を与える | jq '.name // "N/A"' |
| 変数を安全に渡す | jq --arg e prod 'select(.env==$e)' |
10よくある質問(FAQ)
jqとAWS CLIの --query(JMESPath)はどちらを使うべきですか?
まずサーバ側に近い --query で粗く絞ると、転送量やレスポンスサイズを抑えられます。複雑な整形・集計や、AWS以外のJSON(REST APIレスポンス、構造化ログ、kubectl -o json など)を同じ書き味で扱いたい場面では jq が便利です。実務では「--query で絞って jq に渡す」の合わせ技が扱いやすいです。
「Cannot iterate over null」というエラーが出ます。
存在しないキーや null に対して .[] を当てると起きます。.Tags[]? のように ? を付けると、要素が無い場合は静かにスキップします(null安全)。値が null のときに既定値を返したいなら .name // "N/A" のように // を使います。まずは jq . で全体構造を確認し、1段ずつフィルタを足すと原因を切り分けやすいです。
値にダブルクオートが付いてしまい、次のコマンドに渡すと壊れます。
-r(--raw-output)を付けると文字列のダブルクオートが外れ、生の文字列として出力されます。シェル変数への代入やパイプで次コマンドへ渡すときは -r をほぼ必須と考えてください。逆に配列やオブジェクトをそのまま JSON として渡したいときは -r を付けません。
jqのバージョン差は気にすべきですか?
この記事は 1.6/1.7 系で広く使える一般的な機能に絞っています。実務ではまず jq --version で確認し、環境(Amazon Linux / Ubuntu / macOS)に合わせて公式配布のパッケージを入れておけば、日常のユースケースで大きな差は出にくいです。
EMWでは、こうした地味だが確実に効く道具の使いこなしを一緒に積み上げる仲間を募集しています。クラウド運用の基礎から実務まで、カジュアルにお話ししましょう。
相談する