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には --query(JMESPath)が内蔵されています。追加インストール不要で、サーバに近い段階で絞れる(=転送量を減らせる)のが利点。一方 jq は AWS 以外のJSONにも同じ書き味で使えます。まず --query で粗く絞り、複雑な整形・集計は jq に渡す——この使い分けが実務的です。AWS CLI側の詳細はAWS CLI 実践リファレンスにまとめています。
jqは別途インストールします。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'
現場のコツ:いきなり複雑な式を書かず、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]+"))'   # 正規表現でマッチ

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
grep / awk / sort など周辺コマンドとの組み合わせは Linux 実践コマンドリファレンスにまとめています。CloudWatch Logs Insights のような専用クエリがある場合はそちらが速いこともあります。手元に落としたログや、パイプ途中の軽い絞り込みなら jq が手軽です。

08つまづきどころ — クォート・null安全・-r

jqのハマりどころは、ほぼ「シェルのクォート」と「nullの扱い」に集約されます。ここを押さえれば大半のエラーは自己解決できます。

# 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)'
関連記事:AWS CLI 実践リファレンスLinux 実践コマンドリファレンスcurl 実践リファレンス/基礎固めはクラウドエンジニアの基礎(TCP/IP教科書)。こうした地味に効く道具を一緒に磨く仲間を募集しています——採用情報・カジュアル面談はこちら

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では、こうした地味だが確実に効く道具の使いこなしを一緒に積み上げる仲間を募集しています。クラウド運用の基礎から実務まで、カジュアルにお話ししましょう。

相談する
← ブログ一覧へ戻る