「curl で取得した API レスポンスから特定のフィールドだけ抜き出したい」「package.json の依存関係を一覧にしたい」「JSON 形式のログからエラーだけ抽出したい」
こういう作業をターミナルで手軽にこなせるのが jq だ。JSON のスイスアーミーナイフとも呼ばれ、整形・フィルタ・変換・集計まで1つのコマンドでカバーする。
この記事では、基本的なフィルタから実践的なスクリプト活用まで、実際に動かせるコマンド例を交えて解説する。
jq とは
jqはコマンドラインで JSON を処理するための軽量ツールだ。C 言語で書かれていて、外部依存なしの単一バイナリで動作する。GitHub リポジトリでソースコードが公開されており、MIT ライセンスで利用できる。
主な特徴:
- JSON 整形 — ミニファイされた JSON を見やすくフォーマット
- フィールド抽出 — 必要な値だけピンポイントで取り出す
- フィルタリング — 条件に合うデータだけ選別
- 変換・集計 — データ形式の組み替えや計算
- パイプライン — 複数の処理をつなげて複雑な加工も1行で
sed や awk がテキスト処理の定番ツールであるように、jq は JSON 処理の定番だ。API を叩く機会が増えた今、curl と並んで必須のツールと言える。
インストール
# winget(推奨)
winget install jqlang.jq
# WSL を使っている場合は Linux と同じ
# sudo apt install jq
インストールできたらバージョンを確認しよう。
jq --version
jq-1.8.1
基本的な使い方
JSON を整形する
最もシンプルな使い方は . フィルタだ。入力された JSON をそのまま整形して出力する。
echo '{"name":"test","value":42,"active":true}' | jq '.'
{
"name": "test",
"value": 42,
"active": true
}
ファイルから読み込む場合はファイル名を引数に渡す。
jq '.' data.json
フィールドを取得する
ドット記法で特定のフィールドを取り出せる。
echo '{"name":"jq","version":"1.8.1"}' | jq '.name'
"jq"
クォートなしの生の文字列が欲しい場合は -r(raw output)を使う。
echo '{"name":"jq","version":"1.8.1"}' | jq -r '.name'
jq
ネストしたフィールド
ドットをつなげてネストの深い値にアクセスできる。
echo '{"data":{"users":[{"name":"Alice","age":30}]}}' | jq '.data.users[0].name'
"Alice"
配列操作
# 最初の要素
echo '[10,20,30]' | jq '.[0]'
# 最後の要素
echo '[10,20,30]' | jq '.[-1]'
# 配列の長さ
echo '[10,20,30]' | jq 'length'
# スライス(インデックス1から2まで)
echo '[10,20,30,40]' | jq '.[1:3]'
出力形式の制御
# コンパクト出力(1行に圧縮)
echo '{"name":"test","value":42}' | jq -c '.'
# タブインデント
echo '{"name":"test","value":42}' | jq --tab '.'
# ソート済みキーで出力
echo '{"b":2,"a":1}' | jq -S '.'
フィルタとパイプ
jq の真価はフィルタの組み合わせにある。パイプ | でフィルタをつなげて、複雑な処理を組み立てられる。
配列イテレータ
[] で配列の各要素を展開する。
echo '{"users":[{"name":"Alice"},{"name":"Bob"},{"name":"Charlie"}]}' \
| jq '.users[].name'
"Alice"
"Bob"
"Charlie"
select — 条件フィルタ
select() で条件に合う要素だけ抽出する。
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Charlie","age":35}]}' \
| jq '.users[] | select(.age > 28)'
{
"name": "Alice",
"age": 30
}
{
"name": "Charlie",
"age": 35
}
map — 配列の変換
map() は配列の各要素にフィルタを適用して、新しい配列を返す。
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
| jq '.users | map(.name)'
[
"Alice",
"Bob"
]
オブジェクトの構築
必要なフィールドだけ抜き出して新しいオブジェクトを作れる。
echo '{"users":[{"name":"Alice","age":30,"email":"alice@example.com","role":"admin"}]}' \
| jq '.users[] | {name: .name, email: .email}'
{
"name": "Alice",
"email": "alice@example.com"
}
文字列補間
\() で値を文字列に埋め込める。-r と組み合わせるのが定番だ。
echo '{"users":[{"name":"Alice","email":"alice@example.com"},{"name":"Bob","email":"bob@example.com"}]}' \
| jq -r '.users[] | "\(.name): \(.email)"'
Alice: alice@example.com
Bob: bob@example.com
条件分岐
echo '{"status":"ok","data":"hello"}' \
| jq 'if .status == "ok" then .data else "error: \(.status)" end'
"hello"
ソート・グループ化・集計
# ソート
echo '[{"name":"Charlie","age":35},{"name":"Alice","age":30},{"name":"Bob","age":25}]' \
| jq 'sort_by(.age)'
# ユニーク
echo '["apple","banana","apple","cherry","banana"]' | jq 'unique'
# 合計
echo '[10,20,30,40]' | jq 'add'
# 平均値
echo '[10,20,30,40]' | jq 'add / length'
keys と has
# オブジェクトのキー一覧
echo '{"name":"test","version":"1.0","license":"MIT"}' | jq 'keys'
# キーの存在確認
echo '{"name":"test"}' | jq 'has("name")'
実践ユースケース
API レスポンスの加工
curl と組み合わせて API レスポンスから必要な情報を抽出するのは、jq の最も一般的な使い方だ。
curl -s https://api.github.com/repos/jqlang/jq \
| jq '{name: .name, stars: .stargazers_count, forks: .forks_count, license: .license.spdx_id}'
{
"name": "jq",
"stars": 31000,
"forks": 1600,
"license": "MIT"
}
ページネーション付きの API で複数ページの結果を結合する場合:
for page in 1 2 3; do
curl -s "https://api.github.com/users/octocat/repos?per_page=100&page=${page}"
done | jq -s 'flatten | map({name: .name, stars: .stargazers_count}) | sort_by(.stars) | reverse'
package.json の解析
プロジェクトの依存関係をざっと確認したいときに便利だ。
# 依存パッケージの一覧
jq '.dependencies | keys' package.json
[
"next",
"react",
"react-dom"
]
# パッケージ名とバージョンを見やすく表示
jq -r '.dependencies | to_entries[] | "\(.key)@\(.value)"' package.json
next@^16.0.0
react@^19.0.0
react-dom@^19.0.0
# dependencies と devDependencies の合計数
jq '{deps: (.dependencies | length), devDeps: (.devDependencies | length)}' package.json
ログファイルの JSON 解析
JSON Lines 形式(1行1JSON)のログファイルを解析する。
# エラーログだけ抽出してタイムスタンプとメッセージを表示
cat app.log \
| jq -r 'select(.level == "error") | "\(.timestamp) [\(.level)] \(.message)"'
2026-03-08T10:15:30Z [error] Database connection timeout
2026-03-08T10:18:45Z [error] Failed to process request
# エラーレベルごとの件数を集計
cat app.log \
| jq -s 'group_by(.level) | map({level: .[0].level, count: length})'
[
{ "level": "error", "count": 5 },
{ "level": "info", "count": 142 },
{ "level": "warn", "count": 23 }
]
設定ファイルの操作
JSON 形式の設定ファイルをコマンドラインから編集できる。
# 値の更新
jq '.database.port = 5433' config.json > tmp.json && mv tmp.json config.json
# フィールドの追加
jq '.database.ssl = true' config.json > tmp.json && mv tmp.json config.json
# フィールドの削除
jq 'del(.debug)' config.json > tmp.json && mv tmp.json config.json
# 2つの JSON ファイルをマージ(override が優先)
jq -s '.[0] * .[1]' base.json override.json > merged.json
応用テクニック
シェル変数を安全に渡す(--arg / --argjson)
jq のフィルタ内でシェル変数を使いたい場面は多い。直接文字列補間すると壊れやすいので、--arg(文字列)と --argjson(数値・配列・オブジェクト)を使う。
# --arg: 文字列として渡す
username="Alice"
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
| jq --arg name "${username}" '.users[] | select(.name == $name)'
{
"name": "Alice",
"age": 30
}
# --argjson: 数値や配列として渡す(クォートされない)
min_age=28
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
| jq --argjson min "${min_age}" '.users[] | select(.age >= $min)'
# 動的にJSONオブジェクトを構築する
key="version"
value="2.0"
jq -n --arg k "${key}" --arg v "${value}" '{($k): $v}'
{
"version": "2.0"
}
--arg は常に文字列として渡す。数値として比較したい場合は --argjson を使うこと。--arg で渡した "28" と .age(数値の 28)は型が異なるので select(.age >= $min) が期待通りに動かない。
エラーハンドリング(try-catch / ? 演算子)
不完全な JSON や欠損フィールドを処理するときに使う。
# ? 演算子: エラーを無視して処理を続行(try の省略形)
echo 'null' | jq '.foo?'
.foo? は null に対してフィールドアクセスエラーを出さず、null を返す。パイプライン中でデータ構造が不定なとき便利だ。
# // 演算子: null や false のときフォールバック値を返す
echo '{"a":1} {"b":2} {"a":3}' | jq '.a // empty'
1
3
# try-catch: エラー時にフォールバック値を返す
echo '{"data":"not-a-number"}' \
| jq 'try (.data | tonumber) catch "parse error"'
"parse error"
# 配列の要素にフィールドがあったりなかったりする場合
echo '[{"name":"Alice","email":"a@example.com"},{"name":"Bob"}]' \
| jq '.[] | {name, email: (.email // "N/A")}'
{
"name": "Alice",
"email": "a@example.com"
}
{
"name": "Bob",
"email": "N/A"
}
フォーマット文字列(@base64 / @uri / @html / @tsv)
jq には出力を特定フォーマットに変換するビルトインフォーマッタがある。
# @base64: Base64エンコード
echo '{"token":"hello:world"}' | jq -r '.token | @base64'
aGVsbG86d29ybGQ=
# @base64d: Base64デコード
echo '"aGVsbG86d29ybGQ="' | jq -r '@base64d'
hello:world
# @uri: URLエンコード(クエリパラメータの構築に便利)
echo '{"q":"jq filter examples","lang":"ja"}' \
| jq -r '"https://example.com/search?q=\(.q | @uri)&lang=\(.lang)"'
https://example.com/search?q=jq%20filter%20examples&lang=ja
# @tsv: 配列をTSV(タブ区切り)に変換
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' \
| jq -r '.[] | [.name, .age] | @tsv'
Alice 30
Bob 25
巨大 JSON のストリーミング処理(--stream)
数百 MB 以上の JSON ファイルを jq '.' で処理するとメモリに全体を読み込む。--stream を使えば、パス・値のペアとして逐次処理できる。
# --stream: 逐次処理(メモリを節約)
echo '{"users":[{"name":"Alice"},{"name":"Bob"}]}' \
| jq --stream 'select(.[0][-1] == "name") | .[1]'
"Alice"
"Bob"
# 巨大ファイルから特定キーだけ高速に抽出
jq --stream 'select(.[0][0] == "error_count") | .[1]' huge_report.json
ストリーミングモードはフィルタの書き方が通常と異なるので学習コストがあるが、通常モードでメモリ不足になる場合の選択肢として覚えておくと良い。
環境変数にアクセスする($ENV / env)
CI/CD パイプラインやスクリプトで、環境変数を JSON に組み込みたい場面で使える。
export APP_VERSION="1.5.0"
export APP_ENV="production"
# $ENV で環境変数にアクセス
jq -n '{version: $ENV.APP_VERSION, env: $ENV.APP_ENV}'
{
"version": "1.5.0",
"env": "production"
}
# env オブジェクトで全環境変数を確認
jq -n 'env | keys | map(select(startswith("APP_")))'
[
"APP_ENV",
"APP_VERSION"
]
$ENV と env の違い: $ENV.KEY は特定のキーに直接アクセスする。env はオブジェクトとして全環境変数を返すので keys や select でフィルタできる。
スクリプト活用例
jq をシェルスクリプトに組み込むと、JSON データの自動処理が格段に楽になる。xargs と組み合わせれば、抽出した値を別のコマンドに並列で渡すこともできる。
GitHub リポジトリ情報を CSV に変換
#!/bin/bash
# github-repos-to-csv.sh
# 指定ユーザーの公開リポジトリ情報をCSVに変換する
USERNAME="${1:?Usage: $0 <github-username>}"
OUTPUT="repos.csv"
echo "name,stars,forks,language,updated" > "${OUTPUT}"
page=1
while true; do
response=$(curl -s "https://api.github.com/users/${USERNAME}/repos?per_page=100&page=${page}")
count=$(echo "${response}" | jq 'length')
if [ "${count}" -eq 0 ]; then
break
fi
echo "${response}" \
| jq -r '.[] | [.name, .stargazers_count, .forks_count, (.language // "N/A"), .updated_at[:10]] | @csv' \
>> "${OUTPUT}"
page=$((page + 1))
done
total=$(tail -n +2 "${OUTPUT}" | wc -l)
echo "Exported ${total} repositories to ${OUTPUT}"
ポイント:
@csvフィルタが CSV フォーマットへの変換を自動処理する(クォートやエスケープ含む)// "N/A"は alternative operator で、nullまたはfalseの場合にデフォルト値を返す- ページネーションをループで処理して全リポジトリを取得する
複数 JSON ファイルの結合と集計レポート
#!/bin/bash
# merge-json-reports.sh
# reports/ ディレクトリ内のJSONレポートを結合して集計する
REPORT_DIR="${1:?Usage: $0 <report-directory>}"
if [ ! -d "${REPORT_DIR}" ]; then
echo "Error: Directory '${REPORT_DIR}' not found" >&2
exit 1
fi
file_count=$(find "${REPORT_DIR}" -name "*.json" -type f | wc -l)
if [ "${file_count}" -eq 0 ]; then
echo "Error: No JSON files found in '${REPORT_DIR}'" >&2
exit 1
fi
# 全JSONファイルを結合して集計
find "${REPORT_DIR}" -name "*.json" -type f -exec cat {} + \
| jq -s '{
total_files: length,
total_records: (map(.records // 0) | add),
total_errors: (map(.errors // 0) | add),
avg_duration_ms: (map(.duration_ms // 0) | add / length | floor),
statuses: (group_by(.status) | map({status: .[0].status, count: length})),
date_range: {
earliest: (map(.timestamp) | sort | first),
latest: (map(.timestamp) | sort | last)
}
}'
echo ""
echo "Processed ${file_count} files from ${REPORT_DIR}"
ポイント:
-s(slurp)で複数の JSON を1つの配列にまとめるgroup_byとmapの組み合わせでステータス別集計// 0でフィールドが存在しない場合のエラーを防ぐfloorで小数点以下を切り捨て
セキュリティの注意点
信頼できない JSON を処理する場合は、以下の点に注意してほしい。
- バージョンを最新に保つ — パーサーの脆弱性は定期的に発見される。
jq --versionで現在のバージョンを確認し、1.8.1 未満なら即アップデート - 入力サイズを制限する — 巨大な JSON を処理するとメモリを大量消費する。パイプラインの前段で
head -cなどでサイズを制限するか、ストリーミングパーサー(--stream)を検討する - シェルインジェクションに注意 — jq の出力をそのまま
evalやbash -cに渡すのは危険だ。必ずjq -rで取り出した値をクォートして使う
# 危険な例(絶対にやらないこと)
eval $(curl -s https://example.com/config.json | jq -r '.command')
# 安全な例(変数に代入してクォート)
value=$(curl -s https://example.com/config.json | jq -r '.setting')
echo "Setting: ${value}"
関連記事
- curlコマンド完全ガイド —
curl | jqはjqの最も一般的な使い方。API通信の基本からヘッダ操作まで - grep・ripgrep実践ガイド — jqで抽出したデータをさらにテキスト検索したいときに
- sed・awk実践ガイド — JSONからCSVやTSVに変換した後のテキスト加工に
- シェルスクリプト実践ガイド — jqをスクリプトに組み込むときの基盤知識
- xargs実践ガイド — jqで抽出した値を他のコマンドに並列で渡す
- CLIツール完全マップ — CLI ツールの全体像と使い分け
FAQ
jq と yq の違いは?
jq は JSON 専用のプロセッサだ。yq は YAML・XML・TOML も扱えるツールで、内部的に jq ライクな構文を採用している。JSON だけ扱うなら jq のほうが高速で軽量。YAML や複数フォーマットを扱う必要があるなら yq を検討しよう。
jq をインストールせずに使う方法はある?
jq play というオンラインプレイグラウンドがある。ブラウザ上でフィルタを試せるので、インストール前の学習や、複雑なフィルタのデバッグに便利だ。ただし機密データは入力しないこと。
jq で巨大な JSON ファイルを処理するとメモリ不足になる。対処法は?
--stream オプションを使うと、JSON をパス・値のペアとして逐次処理できるためメモリ消費を抑えられる。フィルタの書き方が通常と異なるが、数百 MB 以上のファイルを扱うときは有効な手段だ。
jq の出力を CSV に変換するには?
@csv フォーマッタを使う。配列を作って @csv に渡すだけだ:jq -r '.[] | [.name, .age] | @csv'。クォートやエスケープも自動で処理してくれる。TSV にしたい場合は @tsv を使う。
jq フィルタの中でシェル変数を使うには?
--arg(文字列)または --argjson(数値・配列・オブジェクト)を使う。直接文字列補間すると壊れやすいので、必ずこれらのオプション経由で渡すこと。数値比較には --argjson を使わないと型不一致でフィルタが期待通りに動かない。
jq で null や欠損フィールドを安全に処理するには?
// 演算子(alternative operator)でフォールバック値を指定する:.email // "N/A"。? 演算子でエラーを抑制することもできる:.foo?。try-catch 構文も使える:try (.data | tonumber) catch "parse error"。
Python の json モジュールと jq、どちらを使うべき?
ワンライナーやシェルスクリプトでの加工なら jq が圧倒的に速い。複雑なロジック(条件分岐が多い、DB連携がある等)なら Python のほうが可読性が高い。CI/CD パイプラインでは依存関係が少ない jq が好まれる。
まとめ
jq は JSON をターミナルで自在に操るための必須ツールだ。
- 基本 —
.で整形、.fieldで抽出、-rで生文字列出力 - フィルタ —
selectで条件抽出、mapで変換、パイプで組み合わせ - 実践 — API レスポンス加工、設定ファイル編集、ログ解析
- スクリプト —
@csv変換、-sで結合、シェルスクリプトへの組み込み
curl でデータを取得し、jq で加工し、sed・awk でテキスト整形するという流れを身につければ、ターミナルでのデータ処理が格段に効率化する。まずは curl ... | jq '.' から始めてみてほしい。
関連記事: