- 技術 💻
jqの使い方|逆引き40例と実務レシピを全部実行結果つきで解説
jqとは、JSONを「フィルタ」と呼ぶ短い式で取り出し・絞り込み・加工できるコマンドラインツールです。 APIのレスポンス、クラウドCLIの出力、JSON形式のログなどを、Pythonを書かずにその場で整形・集計できます。
この記事では、1つのサンプルJSONを使い回して「やりたいこと → フィルタ → 出力」を逆引きで40例以上まとめました。掲載している出力は、Ubuntu 24.04 の apt 版 jq 1.7.1 で実際に実行したものです。あわせて 最新の jq 1.8.2(WebAssembly版 jq-wasm 経由)でも同じ出力になることを確認し、1.8で増えた関数(trim、skip、toboolean など)は両バージョンの結果を並べて示します。
jqの使い方早見表(逆引き40)
入力はすべて後述の data.json です。-r は文字列の引用符を外すオプション、-c は1行で出力するオプションです。
| # | やりたいこと | コマンド(フィルタ) | 出力の例 |
|---|---|---|---|
| 1 | 整形して表示 | jq . data.json |
インデントつきJSON |
| 2 | 1行で出力 | jq -c '.members[0]' |
{"id":1,...} |
| 3 | キーの値を取得 | jq '.shop' |
"サンプル商店" |
| 4 | 引用符なしで取得 | jq -r '.shop' |
サンプル商店 |
| 5 | ネストした値 | .members[0].address.city |
"東京" |
| 6 | 最後の要素 | .members[-1].name |
"田中" |
| 7 | 範囲(スライス) | .orders[1:3] |
2〜3番目 |
| 8 | 配列の全要素から1項目 | .members[].name |
3行 |
| 9 | 必要なキーだけの形に | .members[] | {id, name} |
{"id":1,"name":"佐藤"} |
| 10 | 条件で絞り込む | select(.active) |
佐藤、田中 |
| 11 | 複数条件(and/or) | select(.age >= 30 and ...) |
佐藤、田中 |
| 12 | 配列に値を含むか | select(.tags | index("admin")) |
佐藤 |
| 13 | 正規表現で絞り込む | select(.name | test("^佐")) |
sato@... |
| 14 | 各要素を変換 | map(.age) |
[34,28,41] |
| 15 | キー一覧 | keys / keys_unsorted |
並び順が違う |
| 16 | 件数 | length |
4 |
| 17 | キーの有無 | has("email") |
true |
| 18 | 並べ替え | sort_by(.age)、逆順は | reverse |
年齢順 |
| 19 | グループ集計 | group_by(.status) |
状態ごとの件数 |
| 20 | 重複を除く | unique / unique_by(.x) |
一意な値 |
| 21 | 最小・最大の要素 | min_by(.age) / max_by(.age) |
鈴木 / 田中 |
| 22 | 合計 | [...] | add |
4360 |
| 23 | 平均 | add / length |
34.33... |
| 24 | オブジェクト→配列 | to_entries |
[{"key":..,"value":..}] |
| 25 | 配列→オブジェクト | from_entries |
{"a":1,"b":2} |
| 26 | キー名を一括変換 | with_entries(.key |= ascii_upcase) |
{"CITY":...} |
| 27 | 文字列を結合 | .name + "(" + .address.city + ")" |
佐藤(東京) |
| 28 | 文字列に埋め込む | "\(.id): \(.name)" |
1: 佐藤 |
| 29 | CSV / TSV で出力 | [...] | @csv / @tsv |
1,"佐藤","東京" |
| 30 | Base64 / URLエンコード | @base64 / @base64d / @uri |
変換後の文字列 |
| 31 | シェル変数を渡す | --arg / --argjson / $ENV.X |
絞り込み結果 |
| 32 | 入力なしでJSONを作る | jq -n '{...}' |
新しいJSON |
| 33 | 複数JSONを配列にまとめる | -s(--slurp) |
結合した配列 |
| 34 | ネストを平坦化 | .order_id as $o | .items[] |
親子を1行に |
| 35 | 値を更新 | .age |= . + 1 / .email = "..." |
更新後のJSON |
| 36 | キーや要素を削除 | del(.tags, .address) |
削除後のJSON |
| 37 | nullのときの既定値 | .email // "(未登録)" |
既定値で補完 |
| 38 | エラーを握って続行 | try ... catch ... / ? |
エラー時の値 |
| 39 | 畳み込み集計 | reduce .x[] as $i (0; . + $i.qty) |
15 |
| 40 | 先頭N件 / 全パス / 複数ファイル | limit(2; ...) / paths(scalars) / inputs |
各種 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「jqの使い方|逆引き40例と実務レシピを全部実行結果つきで解説」
jqのインストールとバージョン確認
まず jq --version で手元のバージョンを確認します。 2026年9月時点の最新は jq 1.8.2(2026年6月公開)で、1.8系では文字列の前後の空白を削る trim などの関数が加わりました。一方、Ubuntu 24.04 の apt で入るのは 1.7.1 です。この記事の逆引き例は 1.7.1 と 1.8.2 のどちらでも同じ出力になることを確認しています。
| 環境 | インストール方法 | 入るバージョンの目安 |
|---|---|---|
| Ubuntu / Debian | sudo apt install jq |
24.04 は 1.7.1 |
| RHEL系 | sudo dnf install jq |
ディストリビューションに依存 |
| macOS | brew install jq |
Homebrew の最新版 |
| Windows | winget install jqlang.jq |
公式の最新版 |
| 最新版を確実に | 公式サイト(jqlang.org)の配布バイナリ | 1.8.2 |
$ jq --version
jq-1.7
Ubuntu 24.04 のパッケージは 1.7.1 ですが、表示は jq-1.7 になります。 検証に使った環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| OS | Ubuntu 24.04 |
| jq(メイン) | 1.7.1(apt パッケージ 1.7.1-3ubuntu0.24.04.2) |
| jq(最新版の確認) | 1.8.2(npm の jq-wasm 3.0.0 に同梱された WebAssembly ビルド) |
| シェル | bash 5.2 |
この記事で使うサンプルJSON
会員(members)と注文(orders)を持つ、架空のお店のデータです。 以降の例はすべてこのファイルを入力にしています。
{
"shop": "サンプル商店",
"members": [
{"id": 1, "name": "佐藤", "email": "sato@example.com", "age": 34, "tags": ["admin", "dev"], "address": {"city": "東京", "zip": "100-0001"}, "active": true},
{"id": 2, "name": "鈴木", "email": null, "age": 28, "tags": ["dev"], "address": {"city": "大阪", "zip": "530-0001"}, "active": false},
{"id": 3, "name": "田中", "email": "tanaka@example.com", "age": 41, "tags": [], "address": {"city": "東京", "zip": "160-0022"}, "active": true}
],
"orders": [
{"order_id": "A001", "member_id": 1, "status": "shipped", "items": [{"sku": "PEN-01", "qty": 3, "price": 120}, {"sku": "NOTE-02", "qty": 1, "price": 300}]},
{"order_id": "A002", "member_id": 3, "status": "pending", "items": [{"sku": "PEN-01", "qty": 10, "price": 120}]},
{"order_id": "A003", "member_id": 1, "status": "shipped", "items": [{"sku": "BAG-05", "qty": 1, "price": 2500}]},
{"order_id": "A004", "member_id": 2, "status": "cancelled", "items": []}
]
}
フィルタの読み方(3つだけ覚える)
jqのフィルタは「.(いまの値)」「|(左の結果を右へ渡す)」「[](配列を1要素ずつ流す)」の組み合わせです。 シェルのパイプと同じ感覚で、左から右へ読みます。
| 記号 | 意味 | 例 |
|---|---|---|
. |
入力そのもの | jq . で整形表示 |
.key |
キーの値 | .shop |
.[n] |
配列のn番目(0始まり、負数は後ろから) | .members[-1] |
.[] |
配列の全要素を1つずつ出力 | .members[] |
| |
左の出力を右の入力にする | .members[] | .name |
, |
複数の結果を続けて出力 | .shop, .members[0].name |
[ ... ] |
結果を配列にまとめる | [.members[].name] |
{ ... } |
オブジェクトを組み立てる | {name, city: .address.city} |
.members[] のあとは「1人分ずつ」処理される、という点を押さえると、select や map の動きがすっと理解できます。
値を取り出す(#1〜#9)
整形・1行出力・引用符なし
$ jq . data.json | head -8
{
"shop": "サンプル商店",
"members": [
{
"id": 1,
"name": "佐藤",
"email": "sato@example.com",
"age": 34,
$ jq -c '.members[0]' data.json
{"id":1,"name":"佐藤","email":"sato@example.com","age":34,"tags":["admin","dev"],"address":{"city":"東京","zip":"100-0001"},"active":true}
$ jq '.shop' data.json
"サンプル商店"
$ jq -r '.shop' data.json
サンプル商店
シェル変数に入れたり、別のコマンドへ渡したりするときは -r を付けます。 付けないと "サンプル商店" のように引用符まで値に含まれます。
ネスト・インデックス・スライス
$ jq '.members[0].address.city' data.json
"東京"
$ jq '.members[-1].name' data.json
"田中"
$ jq -c '.orders[1:3] | map(.order_id)' data.json
["A002","A003"]
スライス [1:3] は「1番目から3番目の手前まで」です(Pythonと同じ)。
配列の全要素から項目を取り出す
$ jq -r '.members[].name' data.json
佐藤
鈴木
田中
$ jq -c '.members[] | {id, name}' data.json
{"id":1,"name":"佐藤"}
{"id":2,"name":"鈴木"}
{"id":3,"name":"田中"}
$ jq -c '.members[] | {name, city: .address.city}' data.json
{"name":"佐藤","city":"東京"}
{"name":"鈴木","city":"大阪"}
{"name":"田中","city":"東京"}
{id, name} は {id: .id, name: .name} の省略形です。キー名を変えたいときは city: .address.city のように書きます。
条件で絞り込む:select(#10〜#13)
select(条件) は、条件が真のときだけその値を通し、偽なら何も出力しません。
$ jq -r '.members[] | select(.active) | .name' data.json
佐藤
田中
$ jq -r '.members[] | select(.age >= 30 and .address.city == "東京") | .name' data.json
佐藤
田中
$ jq -r '.members[] | select(.tags | index("admin")) | .name' data.json
佐藤
$ jq -r '.members[] | select(.name | test("^佐")) | .email' data.json
sato@example.com
| 条件の書き方 | 意味 |
|---|---|
.x == "a" / .x != "a" |
一致 / 不一致 |
.n >= 30 |
数値の比較 |
A and B / A or B / (A) | not |
かつ / または / 否定 |
.tags | index("admin") |
配列に要素がある(位置が返る。なければ null) |
.s | test("正規表現") |
正規表現にマッチ |
.s | startswith("A") |
前方一致 |
配列を加工・集計する(#14〜#23)
map・keys・length・has
$ jq -c '.members | map(.age)' data.json
[34,28,41]
$ jq -c '.members | map(select(.active) | .id)' data.json
[1,3]
$ jq -c '.members[0] | keys' data.json
["active","address","age","email","id","name","tags"]
$ jq -c '.members[0] | keys_unsorted' data.json
["id","name","email","age","tags","address","active"]
$ jq '.orders | length' data.json
4
$ jq -c '.orders[] | {order_id, n: (.items | length)}' data.json
{"order_id":"A001","n":2}
{"order_id":"A002","n":1}
{"order_id":"A003","n":1}
{"order_id":"A004","n":0}
$ jq -c '.members[] | has("email")' data.json
true
true
true
map(f) は [.[] | f] と同じで、結果を配列のまま返します。 keys はアルファベット順に並べ替え、keys_unsorted は元の順番を保ちます。has("email") は値が null でもキーがあれば true になる点に注意します(鈴木さんは email: null ですが true です)。
sort_by・group_by・unique・min_by/max_by
$ jq -c '.members | sort_by(.age) | map(.name)' data.json
["鈴木","佐藤","田中"]
$ jq -c '.members | sort_by(.age) | reverse | map(.name)' data.json
["田中","佐藤","鈴木"]
$ jq -c '.orders | group_by(.status) | map({status: .[0].status, count: length})' data.json
[{"status":"cancelled","count":1},{"status":"pending","count":1},{"status":"shipped","count":2}]
$ jq -c '.members | unique_by(.address.city) | map(.address.city)' data.json
["大阪","東京"]
$ jq -c '[.orders[].items[].sku] | unique' data.json
["BAG-05","NOTE-02","PEN-01"]
$ jq -c '.members | min_by(.age), max_by(.age) | .name' data.json
"鈴木"
"田中"
group_by は「配列の配列」を返すので、map({key: .[0].x, count: length}) で集計表の形にするのが定番です。group_by と unique は結果がキー順に並べ替わります。
add で合計・平均
$ jq '[.orders[].items[] | .qty * .price] | add' data.json
4360
$ jq -c '.orders[] | {order_id, total: ([.items[] | .qty * .price] | add // 0)}' data.json
{"order_id":"A001","total":660}
{"order_id":"A002","total":1200}
{"order_id":"A003","total":2500}
{"order_id":"A004","total":0}
$ jq -c '[.members[] | .age] | add / length' data.json
34.333333333333336
$ jq -c '[.members[] | .age] | {min: min, max: max, sum: add}' data.json
{"min":28,"max":41,"sum":103}
空配列の add は null になるため、注文A004のように明細がないものは // 0 で0に置き換えています。
オブジェクトを変換する(#24〜#26)
to_entries はオブジェクトを {key, value} の配列に、from_entries はその逆に変換します。 with_entries(f) は「to_entries → map(f) → from_entries」をまとめたものです。
$ jq -c '.members[0].address | to_entries' data.json
[{"key":"city","value":"東京"},{"key":"zip","value":"100-0001"}]
$ jq -c '[{"key":"a","value":1},{"key":"b","value":2}] | from_entries' <<< 'null'
{"a":1,"b":2}
$ jq -c '.members[0].address | with_entries(.key |= ascii_upcase)' data.json
{"CITY":"東京","ZIP":"100-0001"}
必要なキーだけを階層ごと残すなら pick(1.7以降)が便利です。
$ jq -c '.members[0] | pick(.name, .address.city)' data.json
{"name":"佐藤","address":{"city":"東京"}}
IDで引ける辞書を作って結合することもできます。INDEX(f) は、f の値をキーにしたオブジェクトを作ります。
$ jq -c '(.members | INDEX(.id|tostring)) as $m | .orders[] | {order_id, name: $m[.member_id|tostring].name}' data.json
{"order_id":"A001","name":"佐藤"}
{"order_id":"A002","name":"田中"}
{"order_id":"A003","name":"佐藤"}
{"order_id":"A004","name":"鈴木"}
文字列を組み立てる・CSV/TSVで出力する(#27〜#30)
$ jq -r '.members[] | .name + "(" + .address.city + ")"' data.json
佐藤(東京)
鈴木(大阪)
田中(東京)
$ jq -r '.members[] | "\(.id): \(.name) \(.age)歳"' data.json
1: 佐藤 34歳
2: 鈴木 28歳
3: 田中 41歳
$ jq -r '.members[] | .tags | join(",")' data.json
admin,dev
dev
+ は文字列同士しか結合できませんが、"\(式)"(文字列補間)なら数値もそのまま埋め込めます。 3行目は田中さんの tags が空配列なので空行になります。
$ jq -r '.members[] | [.id, .name, .address.city] | @csv' data.json
1,"佐藤","東京"
2,"鈴木","大阪"
3,"田中","東京"
$ jq -r '.members[] | [.id, .name, .address.city] | @tsv' data.json
1 佐藤 東京
2 鈴木 大阪
3 田中 東京
@csv / @tsv は「配列」を1行に変換します。必ず -r と組み合わせます。 ヘッダー行が必要なら、["id","name","city"], (.members[] | [...]) | @csv のように先頭に見出しの配列を出力します。
| 書式 | 用途 | 例の出力 |
|---|---|---|
@csv |
CSV(文字列は " で囲む) |
1,"佐藤","東京" |
@tsv |
タブ区切り | 1(タブ)佐藤(タブ)東京 |
@base64 / @base64d |
Base64 エンコード / デコード | 44K144Oz... |
@uri |
URLエンコード | %E3%82%B5... |
@sh |
シェル用に安全にクォート | 'a b' |
@json |
JSON文字列に変換 | "{\"id\":1,...}" |
@html |
HTMLエスケープ | < など |
$ jq -r '.shop | @base64' data.json
44K144Oz44OX44Or5ZWG5bqX
$ jq -r '.shop | @base64 | @base64d' data.json
サンプル商店
$ jq -r '.shop | @uri' data.json
%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E5%95%86%E5%BA%97
$ jq -r '.[] | @sh "echo \(.)"' <<< '["a b","c'"'"'d"]'
echo 'a b'
echo 'c'\''d'
シェル変数を渡す・入力なしでJSONを作る(#31〜#32)
シェル変数をフィルタの文字列に直接埋め込むのではなく、--arg で渡します。 引用符やスペースを含む値でも安全に扱えます。
$ jq -r --arg city 東京 '.members[] | select(.address.city == $city) | .name' data.json
佐藤
田中
$ jq -c --argjson min 30 '.members | map(select(.age >= $min) | .name)' data.json
["佐藤","田中"]
$ MY_CITY=大阪 jq -r '.members[] | select(.address.city == $ENV.MY_CITY) | .name' data.json
鈴木
$ jq -n -c --arg id t-9 --arg title "新規タスク" '{id: $id, title: $title, done: false}'
{"id":"t-9","title":"新規タスク","done":false}
| 渡し方 | 型 | 使いどころ |
|---|---|---|
--arg name 値 |
常に文字列 | 名前・IDなど |
--argjson name 値 |
JSONとして解釈(数値・真偽値・配列) | しきい値の数値など |
$ENV.NAME / env.NAME |
文字列 | 環境変数をそのまま使う |
--args a b c → $ARGS.positional |
文字列の配列 | 可変個の引数 |
--slurpfile name file |
ファイル内のJSONの配列 | 別ファイルと突き合わせ |
--arg の値は数値に見えても文字列です("x":"1")。数値として比較するなら --argjson を使います。-n は入力を読まずに null から始めるオプションで、JSONを組み立てるときに使います。
値を更新する・削除する(#35〜#36)
= は値を置き換え、|= は「今の値をもとに」更新します。 jqは元のファイルを書き換えないので、結果を別ファイルに保存します。
$ jq -c '.members[0] | .age |= . + 1 | {name, age}' data.json
{"name":"佐藤","age":35}
$ jq -c '.members[1] | .email = "suzuki@example.com" | {name, email}' data.json
{"name":"鈴木","email":"suzuki@example.com"}
$ jq -c '.members[] |= (.active = true) | .members | map(.active)' data.json
[true,true,true]
$ jq -c '.orders[0].items[].price |= . + 10 | .orders[0].items | map(.price)' data.json
[130,310]
$ jq -c '.members[0] | del(.tags, .address) ' data.json
{"id":1,"name":"佐藤","email":"sato@example.com","age":34,"active":true}
$ jq -c 'del(.orders[] | select(.status == "cancelled")) | .orders | map(.order_id)' data.json
["A001","A002","A003"]
ファイルを更新したいときは、一時ファイルに書いてから置き換えます。 jq ... data.json > data.json と同じファイルにリダイレクトすると、jqが読む前に中身が空になります。
$ jq '.members[0].address' data.json > tmp.json && mv tmp.json out.json && cat out.json
{
"city": "東京",
"zip": "100-0001"
}
nullとエラーを扱う(#37〜#38)
A // B は「Aが null か false か結果なしなら B」を返す代替演算子です。
$ jq -r '.members[] | .email // "(未登録)"' data.json
sato@example.com
(未登録)
tanaka@example.com
$ jq -c '.members[] | .tags[0] // "no-tag"' data.json
"admin"
"dev"
"no-tag"
$ jq -c '.members[] | .email // empty | split("@")[1]' data.json
"example.com"
"example.com"
empty は「何も出力しない」値なので、null の行を読み飛ばすのに使えます。
$ jq -c '.shop | try tonumber catch "数値ではありません"' data.json
"数値ではありません"
$ jq -c '.shop | tonumber?' data.json; echo "exit=$?"
exit=0
$ jq '.members[]?.name' <<< '{"members": null}'
try A catch B はAがエラーのときBを返し、A? はエラーを黙って捨てます。 最後の例は何も出力されず、エラーにもなりません。
スクリプトで「見つからなかったら失敗」にしたいときは -e を付けます。 最後の出力が null / false、または出力がないと終了コードが0以外になります。
$ jq -e '.members[] | select(.id == 99)' data.json; echo "exit=$?"
exit=4
$ jq -e '.shop' data.json > /dev/null; echo "exit=$?"
exit=0
畳み込み・先頭N件・パス一覧(#39〜#40)
$ jq 'reduce .orders[].items[] as $i (0; . + $i.qty)' data.json
15
$ jq -c 'reduce .orders[] as $o ({}; .[$o.status] += 1)' data.json
{"shipped":2,"pending":1,"cancelled":1}
$ jq -c '[limit(2; .members[].name)]' data.json
["佐藤","鈴木"]
$ jq -c '.members[0] | [paths(scalars) | join(".")]' data.json
["id","name","email","age","tags.0","tags.1","address.city","address.zip","active"]
reduce 流れ as $x (初期値; 更新式) は、ループで合計や件数を数える処理です。.[$o.status] += 1 のように、null に += 1 すると 1 になる性質を使うと、件数集計が短く書けます。
paths(scalars) は、末端の値(文字列・数値など)までのパスをすべて返します。 知らないJSONの構造を把握するときに役立ちます。なお、以前のマニュアルに載っていた leaf_paths は、今回の 1.7.1 と 1.8.2 のどちらでも未定義エラー(leaf_paths/0 is not defined)でした。leaf_paths の代わりに paths(scalars) を使うと、両バージョンで動きます。
ネストを平坦化する(#34)
親の値を変数に入れてから子の配列を展開すると、「注文×明細」を1行ずつにできます。
$ jq -c '[.orders[] | .order_id as $o | .items[] | {order_id: $o, sku}]' data.json
[{"order_id":"A001","sku":"PEN-01"},{"order_id":"A001","sku":"NOTE-02"},{"order_id":"A002","sku":"PEN-01"},{"order_id":"A003","sku":"BAG-05"}]
$ jq -c '.orders | [.[] | .items[]] | group_by(.sku) | map({sku: .[0].sku, qty: (map(.qty) | add)})' data.json
[{"sku":"BAG-05","qty":1},{"sku":"NOTE-02","qty":1},{"sku":"PEN-01","qty":13}]
配列の配列を1段にするだけなら flatten が使えます。
$ jq -c '[.orders[].items] | flatten | length' data.json
4
複数ファイル・JSON Linesを扱う(#33・#40)
ページングされたAPIの結果を想定した2つのファイルを使います。
$ jq -c '.' page1.json page2.json
{"page":1,"items":[{"id":"t-1","title":"備品の発注"},{"id":"t-2","title":"議事録の作成"}],"next":2}
{"page":2,"items":[{"id":"t-3","title":"在庫の棚卸し"}],"next":null}
jqは複数ファイルを渡すと1つずつ処理します。まとめて扱うには -s(slurp)か -n + inputs を使います。
$ jq -s -c 'map(.items) | add' page1.json page2.json
[{"id":"t-1","title":"備品の発注"},{"id":"t-2","title":"議事録の作成"},{"id":"t-3","title":"在庫の棚卸し"}]
$ jq -n -c '[inputs | .items[]] | length' page1.json page2.json
3
$ jq -r 'input_filename' page1.json page2.json
page1.json
page2.json
$ jq -c --slurpfile p page2.json '{first: .items, second: $p[0].items} | [.first[], .second[]] | map(.id)' page1.json
["t-1","t-2","t-3"]
| 方法 | 動き | 向いている場面 |
|---|---|---|
| 複数ファイルをそのまま | 1ファイル(1値)ずつ処理 | 各ファイルから同じ項目を取る |
-s |
全入力を1つの配列にしてから処理 | 件数が少なく、まとめて集計したい |
-n + inputs |
1値ずつ読みながら処理 | 大きなログを reduce で集計 |
--slurpfile |
別ファイルを変数として読む | マスタとの突き合わせ |
出力の形を変えるオプション
| オプション | 意味 | 例 |
|---|---|---|
-r |
文字列の引用符を外す | サンプル商店 |
-c |
1行で出力 | {"a":1} |
-j |
-r に加えて改行も付けない |
佐藤 鈴木 田中 |
-S |
キーを並べ替える | 差分比較に便利 |
--tab / --indent n |
インデントをタブ / n個の空白に | 見やすさの調整 |
--raw-output0 |
NUL区切りで出力(1.7以降) | xargs -0 と組み合わせ |
-e |
結果で終了コードを変える | スクリプトの判定 |
-f file |
フィルタをファイルから読む | 長いフィルタ、Windows |
$ jq -rj '.members[] | .name, " "' data.json; echo
佐藤 鈴木 田中
$ jq --indent 4 '.members[0].address' data.json
{
"city": "東京",
"zip": "100-0001"
}
$ jq -r --raw-output0 '.members[].name' data.json | xargs -0 -n1 echo name:
name: 佐藤
name: 鈴木
name: 田中
$ cat active.jq
.members[]
| select(.active)
| .name
$ jq -r -f active.jq data.json
佐藤
田中
Windows の cmd.exe ではシングルクォートでフィルタを囲めません。 フィルタを -f でファイルから読むと、シェルごとのクォートの違いを気にせずに済みます。
実務レシピ:そのまま使える3パターン
レシピ1:AWS CLI風のJSONから必要な項目をTSVで出す
aws ec2 describe-instances のような「配列の中の配列」の出力を想定した架空のデータ(instances.json)です。
{
"Reservations": [
{"Instances": [
{"InstanceId": "i-0aaa111", "InstanceType": "t3.micro", "State": {"Name": "running"}, "PrivateIpAddress": "10.0.1.10", "Tags": [{"Key": "Name", "Value": "web-01"}, {"Key": "env", "Value": "dev"}]},
{"InstanceId": "i-0bbb222", "InstanceType": "t3.small", "State": {"Name": "stopped"}, "PrivateIpAddress": "10.0.1.11", "Tags": [{"Key": "env", "Value": "dev"}]}
]},
{"Instances": [
{"InstanceId": "i-0ccc333", "InstanceType": "m7g.large", "State": {"Name": "running"}, "PrivateIpAddress": "10.0.2.20", "Tags": [{"Key": "Name", "Value": "batch-01"}]}
]}
]
}
$ jq -r '.Reservations[].Instances[] | [.InstanceId, .InstanceType, .State.Name, .PrivateIpAddress, ((.Tags // []) | from_entries | .Name // "-")] | @tsv' instances.json
i-0aaa111 t3.micro running 10.0.1.10 web-01
i-0bbb222 t3.small stopped 10.0.1.11 -
i-0ccc333 m7g.large running 10.0.2.20 batch-01
$ jq -r '.Reservations[].Instances[] | select(.State.Name == "running") | .InstanceId' instances.json
i-0aaa111
i-0ccc333
ポイントは from_entries です。 [{"Key":"Name","Value":"web-01"}] 形式のタグ配列も from_entries で {"Name":"web-01"} に変換できます(Key/Value の大文字表記も受け付けます)。Nameタグがないインスタンスは // "-" で補完しています。
レシピ2:ページングされたAPIレスポンスを1つに結合する
$ jq -s -c '{total: (map(.items | length) | add), items: (map(.items) | add)}' page1.json page2.json
{"total":3,"items":[{"id":"t-1","title":"備品の発注"},{"id":"t-2","title":"議事録の作成"},{"id":"t-3","title":"在庫の棚卸し"}]}
$ cat page1.json page2.json | jq -c '.items[]'
{"id":"t-1","title":"備品の発注"}
{"id":"t-2","title":"議事録の作成"}
{"id":"t-3","title":"在庫の棚卸し"}
1つの配列にしたいなら -s、1件ずつ次の処理に流したいなら cat ... | jq '.items[]' と使い分けます。後者は結果がJSON Lines(1行1JSON)になります。
レシピ3:JSON Lines のログを集計する
1行に1つのJSONが並ぶログ(app.jsonl)です。
{"ts":"2026-09-28T10:00:01Z","level":"INFO","path":"/api/orders","status":200,"ms":35}
{"ts":"2026-09-28T10:00:02Z","level":"ERROR","path":"/api/orders","status":500,"ms":1203}
{"ts":"2026-09-28T10:00:03Z","level":"INFO","path":"/api/members","status":200,"ms":22}
{"ts":"2026-09-28T10:00:04Z","level":"WARN","path":"/api/members","status":404,"ms":8}
{"ts":"2026-09-28T10:00:05Z","level":"INFO","path":"/api/orders","status":200,"ms":41}
$ jq -r 'select(.level == "ERROR") | [.ts, .path, .status] | @tsv' app.jsonl
2026-09-28T10:00:02Z /api/orders 500
$ jq -s -c 'group_by(.level) | map({level: .[0].level, count: length})' app.jsonl
[{"level":"ERROR","count":1},{"level":"INFO","count":3},{"level":"WARN","count":1}]
$ jq -s -c 'group_by(.path) | map({path: .[0].path, n: length, avg_ms: (map(.ms) | add / length), max_ms: (map(.ms) | max)})' app.jsonl
[{"path":"/api/members","n":2,"avg_ms":15,"max_ms":22},{"path":"/api/orders","n":3,"avg_ms":426.3333333333333,"max_ms":1203}]
$ jq -n -c 'reduce inputs as $l ({}; .[$l.path] += 1)' app.jsonl
{"/api/orders":3,"/api/members":2}
$ jq -c 'select(.ms > 100)' app.jsonl
{"ts":"2026-09-28T10:00:02Z","level":"ERROR","path":"/api/orders","status":500,"ms":1203}
ログが大きいときは -s よりも -n + reduce inputs が向いています。 -s は全行をメモリ上の配列にしてから処理しますが、reduce inputs は1行ずつ読みながら集計します。
jq 1.8 で使えるようになった関数(1.7.1と比較)
jq 1.8.0(2025年6月)で、文字列の前後の空白を削る trim や、先頭N件を飛ばす skip などが加わりました。 同じフィルタを 1.7.1 と 1.8.2 で実行した結果です。
| 機能 | フィルタ | 1.7.1 | 1.8.2 |
|---|---|---|---|
| 前後の空白を削る | " 注文 A001 " | trim, ltrim, rtrim |
未定義エラー | "注文 A001" "注文 A001 " " 注文 A001" |
| 前後から指定文字列を削る | "__tag__" | trimstr("__") |
未定義エラー | "tag" |
| 先頭N件を飛ばす | [skip(2; 1,2,3,4,5)] |
未定義エラー | [3,4,5] |
| 文字列を真偽値に | ["true","false",true] | map(toboolean) |
未定義エラー | [true,false,true] |
| URLデコード | "%E6%9D%B1%E4%BA%AC" | @urid |
urid is not a valid format |
"東京" |
| 式の結果を合計 | add(1,2,3) |
未定義エラー | 6 |
| 絶対値 | -5 | abs |
5 |
5 |
| elseを省略した if | if . > 0 then "positive" end(入力 -1) |
-1 |
-1 |
$変数 をキーに |
"k" as $key | {$key: 1} |
{"k":1} |
{"k":1} |
1.7.1 で trim を使ったときのエラーは次のとおりです。
$ jq -nc '" 注文 A001 " | trim'
jq: error: trim/0 is not defined at <top-level>, line 1:
" 注文 A001 " | trim
jq: 1 compile error
1.8 ではエラーメッセージに列番号と ^^^^ の位置表示が付き、どこが悪いかが一目でわかります。
$ jq -c '.members[0] | [leaf_paths | map(tostring) | join(".")]' data.json # jq 1.8.2
jq: error: leaf_paths/0 is not defined at <top-level>, line 1, column 16:
.members[0] | [leaf_paths | map(tostring) | join(".")]
^^^^^^^^^^
jq: 1 compile error
チームで共有するスクリプトでは、1.7系でも動く書き方にしておくと安心です。 例えば trim の代わりに sub("^\\s+"; "") | sub("\\s+$"; "") を使えます。また、今回の検証では null に ltrimstr("x") を使うと、1.7.1 は null を返し、1.8.2 は startswith() requires string inputs エラーになりました。入力が null になりうる場合は (.x // "") | ltrimstr("v") のように既定値を入れておくと、両方で同じ動きになります。
よくあるエラーと直し方
| エラーメッセージ | 主な原因 | 直し方 |
|---|---|---|
parse error: Invalid numeric literal |
入力がJSONではない(key=value、シングルクォートのJSONなど) |
入力を確認。APIならHTMLやエラー文が返っていないか見る |
Cannot index object with number |
オブジェクトに [0] を使った |
keys で構造を確認し .key で取る |
Cannot iterate over null |
存在しないキーに [] を使った |
.x[]? か (.x // [])[] |
number (1) and string (":") cannot be added |
数値と文字列を + で結合 |
tostring か "\(.id):..." |
xxx/0 is not defined |
関数名の誤り、またはバージョンにない関数 | jq --version とマニュアルを確認 |
$ jq '.name' <<< 'name=sato'
jq: parse error: Invalid numeric literal at line 2, column 0
$ jq '.a' <<< "{'a': 1}"
jq: parse error: Invalid numeric literal at line 1, column 5
$ jq '.members[0].name' <<< '{"members": {"name": "x"}}'
jq: error (at <stdin>:1): Cannot index object with number
$ jq '.members[].name' <<< '{"members": null}'
jq: error (at <stdin>:1): Cannot iterate over null (null)
$ jq -r '.members[] | .id + ":" + .name' data.json
jq: error (at data.json:14): number (1) and string (":") cannot be added
$ jq -r '.members[] | (.id|tostring) + ":" + .name' data.json
1:佐藤
2:鈴木
3:田中
Invalid numeric literal は「入力がJSONとして読めない」という意味です。JSONの文字列はダブルクォートで囲む決まりなので、{'a': 1} のようなシングルクォートの入力はエラーになります。
よくある質問
Q. jqでファイルを直接書き換える(上書きする)オプションはありますか?
A. jq本体にはありません。jq '...' in.json > tmp.json && mv tmp.json in.json のように一時ファイルを経由します。同じファイルへ直接リダイレクトすると、読み込む前に中身が空になります。
Q. -r を付けるべきか迷います。
A. 結果を人が読む・シェル変数に入れる・@csv で出す場合は -r、結果を別のjqやプログラムにJSONとして渡す場合は付けない、が目安です。
Q. 大きなJSONファイルでも使えますか?
A. 使えます。JSON Linesのように1行1値のデータなら、-n と reduce inputs を使うと1件ずつ読みながら集計できます。1つの巨大な配列を少しずつ読みたいときは --stream オプションもあります。
Q. jq 1.7 と 1.8 のどちらを使えばよいですか?
A. 新しく入れるなら公式配布の最新版(1.8.2)がおすすめです。この記事の逆引き例は両方で同じ出力になることを確認しています。trim や skip など1.8で増えた関数は、1.7系の環境では未定義エラーになります。
まとめ
- jqは「
.」「|」「[]」の組み合わせでJSONを左から右へ加工するツールです。 - 取り出しは
.keyと.[]、絞り込みはselect、変換はmapが基本です。 - 集計は
group_by/add/reduce、CSV/TSV化は@csv/@tsvと-rを組み合わせます。 - シェル変数は
--arg/--argjsonで渡すと、クォートの問題が起きません。 - null対策は
//と?、スクリプトでの判定は-eを使います。 - 複数ファイルやログは
-sか-n+inputsでまとめて扱えます。 - jq 1.8 では
trim・skip・tobooleanなどが追加され、エラー表示も見やすくなりました。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「jqの使い方|逆引き40例と実務レシピを全部実行結果つきで解説」
株式会社RJCでは、JSONの加工やログの集計のような小さな工夫を、プロジェクトの中で仲間と教え合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。