jq 入門:コマンドラインで JSON を自在に操る

よるほろぐ編集部

· 約 22 分

API のレスポンスや設定ファイルを眺めていると、JSON をターミナル上でサクッと加工したくなる場面が頻繁にあります。python3 -m json.tool で整形はできても、特定フィールドの抽出や配列のフィルタリングとなると途端に手間がかかります。

そこで便利なのが jq です。jq は JSON を入力として受け取り、フィルタを適用して変換・抽出・整形した結果を出力するコマンドラインプロセッサです。curl と組み合わせて API レスポンスから必要なフィールドだけ抜き出す、JSON のログファイルを集計する、といった用途で広く使われています。

jq のプログラムは「フィルタ」です。フィルタは入力を受け取って出力を生成します。単純な . (ドット)から始まり、フィルタ同士を | でつないでパイプラインを構成するのが基本的な使い方です。

インストール

主要なパッケージマネージャで配布されているため、環境を問わずすぐに使えます。

# Debian/Ubuntu
sudo apt install jq

# Fedora/RHEL/AlmaLinux
sudo dnf install jq

# macOS (Homebrew)
brew install jq

インストール後、バージョンを確認しておきましょう。

jq --version
# jq-1.7.1  など

基本:JSON を整形する

jq の最もシンプルな使い方は、JSON の 整形(pretty-print) です。jq '.' に JSON を渡すだけで、インデント付きの読みやすい形式に変換されます。

echo '{"name":"Alice","age":30,"active":true}' | jq '.'
{
  "name": "Alice",
  "age": 30,
  "active": true
}

. は「identity(恒等)フィルタ」と呼ばれ、入力をそのまま出力します。jq はデフォルトで pretty-print するため、生の JSON を整形するだけでもすぐ役立ちます。

フィールドの取得

オブジェクトのフィールド .key

.foo の形でオブジェクトの特定フィールドを取り出せます。キーが存在しない場合は null が返ります。

echo '{"name":"Alice","age":30}' | jq '.name'
"Alice"

ネストしたフィールドは .a.b で辿れます。

echo '{"user":{"name":"Alice","city":"Tokyo"}}' | jq '.user.city'
"Tokyo"

配列のインデックス .[N]

配列は .[0] で先頭、.[-1] で末尾の要素を取得できます。

echo '["apple","banana","cherry"]' | jq '.[1]'
"banana"

配列の全要素 .[]

.[] は配列の全要素を 個別に 出力するイテレータです。

echo '[{"name":"Alice"},{"name":"Bob"}]' | jq '.[]'
{"name": "Alice"}
{"name": "Bob"}

配列の各要素からフィールドを取り出したいときは .array[].field と書けます。

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | jq '.[].name'
"Alice"
"Bob"
.[] と .[0] の違い

.[] は複数の値を別々のアウトプットとして出力します。[.[] ] のように [ ] で囲うと配列として収集できます。一方 .[0] は最初の 1 要素のみを返します。フィルタをつないだときの挙動に影響するので意識しておきましょう。

フィルタとパイプ |

jq 内でも Unix シェルと同様に | でフィルタをつなげます。左のフィルタの出力が右のフィルタの入力になります。

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | jq '.[] | .name'
"Alice"
"Bob"

.[] | .name は「配列の各要素を展開して、それぞれの .name を取り出す」というパイプラインです。

複数フィールドを同時に取り出したいときはカンマで並べます。

echo '{"name":"Alice","age":30,"city":"Tokyo"}' | jq '.name, .age'
"Alice"
30

配列操作

map(f) — 全要素に変換を適用

map(f) は配列の各要素にフィルタ f を適用し、結果を配列として返します。[.[] | f] と同義です。

echo '[1,2,3,4,5]' | jq 'map(. * 2)'
[2, 4, 6, 8, 10]

select(f) — 条件で絞り込む

select(f)f が真のときだけ入力を通過させます。map() と組み合わせて配列のフィルタリングに使います。

echo '[{"name":"Alice","age":30},{"name":"Bob","age":17},{"name":"Carol","age":25}]' \
  | jq 'map(select(.age >= 20))'
[
  {"name": "Alice", "age": 30},
  {"name": "Carol", "age": 25}
]

length — 長さ・要素数

配列なら要素数、文字列なら文字数、オブジェクトならキー数を返します。

echo '[1,2,3,4,5]' | jq 'length'
5

sort_by(f) — フィールドでソート

sort_by(.field) で配列を特定フィールドの値順に並べ替えます。

echo '[{"name":"Carol","age":25},{"name":"Alice","age":30},{"name":"Bob","age":17}]' \
  | jq 'sort_by(.age)'
[
  {"name": "Bob", "age": 17},
  {"name": "Carol", "age": 25},
  {"name": "Alice", "age": 30}
]

unique / unique_by(f) — 重複除去

unique は配列の重複要素を除いてソートした配列を返します。

echo '[3,1,2,1,3,2]' | jq 'unique'
[1, 2, 3]

group_by(f) — グルーピング

group_by(.field) は配列を指定フィールドの値でグループ化し、2次元配列として返します。

echo '[{"type":"A","val":1},{"type":"B","val":2},{"type":"A","val":3}]' \
  | jq 'group_by(.type)'
[
  [{"type": "A", "val": 1}, {"type": "A", "val": 3}],
  [{"type": "B", "val": 2}]
]

add — 要素の集約

add は配列の要素を足し合わせます。数値なら合計、文字列なら連結、配列なら結合です。

echo '[1,2,3,4,5]' | jq 'add'
15

オブジェクトの構築と変換

新しいオブジェクトを作る {key: expr}

{} 構文で必要なフィールドだけを抽出した新しいオブジェクトを作れます。

echo '{"id":1,"name":"Alice","email":"alice@example.com","age":30}' \
  | jq '{name: .name, email: .email}'
{
  "name": "Alice",
  "email": "alice@example.com"
}

キー名と参照フィールド名が同じなら {name, email} と短縮できます。

パイプで複数変換

パイプとオブジェクト構築を組み合わせると、配列の各要素を変換した新しい配列を作れます。

echo '[{"id":1,"name":"Alice","score":85},{"id":2,"name":"Bob","score":72}]' \
  | jq 'map({name, passed: (.score >= 80)})'
[
  {"name": "Alice", "passed": true},
  {"name": "Bob", "passed": false}
]

to_entries / from_entries

to_entries はオブジェクトを {"key": k, "value": v} の配列に変換します。from_entries は逆変換です。キーや値を操作したいときに便利です。

echo '{"a":1,"b":2,"c":3}' | jq 'to_entries'
[
  {"key": "a", "value": 1},
  {"key": "b", "value": 2},
  {"key": "c", "value": 3}
]

値を一律加工して戻す例:

echo '{"a":1,"b":2,"c":3}' | jq 'to_entries | map(.value *= 10) | from_entries'
{"a": 10, "b": 20, "c": 30}

よく使うオプション早見表

オプション短縮形意味
--raw-output-r文字列をクォートなしで出力する
--compact-output-c1行のコンパクト形式で出力する
--slurp-s複数の JSON 入力を配列として読み込む
--null-input-n入力を読まず null をインプットとする
--arg name val文字列変数 $name を定義する
--argjson name valJSON 値の変数 $name を定義する
--exit-status-e最後の出力が false/null なら exit 1
--sort-keys-Sオブジェクトのキーをソートして出力する

-r(raw output)

デフォルトでは文字列はダブルクォートで囲まれた JSON 形式で出力されます。シェルスクリプトで変数に代入するなど、クォートが不要な場合は -r を付けます。

echo '{"name":"Alice"}' | jq '.name'
# → "Alice"  ← クォートあり

echo '{"name":"Alice"}' | jq -r '.name'
# → Alice    ← クォートなし
-r を忘れると文字列にクォートが付く

シェルスクリプトで NAME=$(jq '.name' data.json) とすると NAME の値は "Alice" (クォート込み)になります。後続のコマンドに渡すと意図しない動作になることがあります。文字列を取り出す場合は -r を付けることを習慣にしましょう。

-c(compact output)

改行・インデントを省いた 1 行形式で出力します。ログとして記録したいときや、次のコマンドに渡したいときに使います。

echo '{"name":"Alice","age":30}' | jq -c '.'
# → {"name":"Alice","age":30}

-s(slurp)

複数の JSON を 1 つの配列にまとめて読み込みます。複数のファイルや改行区切りの JSON 行を集計するときに使います。

echo '{"x":1}
{"x":2}
{"x":3}' | jq -s 'map(.x) | add'
# → 6

--arg / --argjson

jq フィルタ内で外部の値を変数として参照したいときに使います。--arg は文字列、--argjson は JSON 値として渡します。

jq --arg user "Alice" '.[] | select(.name == $user)' users.json

jq --argjson min 20 '.[] | select(.age >= $min)' users.json

文字列・データ変換関数

@csv / @tsv — CSV/TSV 形式に変換

配列を CSV または TSV 文字列にフォーマットします。-r と組み合わせて使います。

echo '[["Alice",30,"Tokyo"],["Bob",25,"Osaka"]]' \
  | jq -r '.[] | @csv'
"Alice",30,"Tokyo"
"Bob",25,"Osaka"

join(str) — 配列を結合

join(",") で配列要素を区切り文字つなぎにします。

echo '["apple","banana","cherry"]' | jq -r 'join(", ")'
# → apple, banana, cherry

split(str) — 文字列を分割

文字列を区切り文字で分割して配列にします。

echo '"a,b,c"' | jq 'split(",")'
# → ["a","b","c"]

ascii_downcase / ascii_upcase

英字を小文字・大文字に変換します。

echo '"Hello World"' | jq 'ascii_downcase'
# → "hello world"

test(regex) — 正規表現マッチ

文字列が正規表現にマッチするかどうかを真偽値で返します。select() と組み合わせてフィルタリングに使えます。

echo '[{"email":"alice@example.com"},{"email":"bob@test.org"}]' \
  | jq 'map(select(.email | test("@example\\.com$")))'
[{"email": "alice@example.com"}]

has(key) — キーの存在確認

オブジェクトが指定のキーを持つかどうかを返します。

echo '{"name":"Alice","age":30}' | jq 'has("age")'
# → true

contains(val) — 包含確認

入力が指定の値を含むかどうかを確認します。文字列ならサブストリング、配列なら要素の包含、オブジェクトならサブセットの確認になります。

echo '{"name":"Alice","tags":["admin","editor"]}' \
  | jq 'contains({"tags":["admin"]})'
# → true

実践例

curl で API を叩いて必要なフィールドだけ抽出

GitHub API からリポジトリ情報を取得して、名前と star 数だけ出力する例です。

curl -s "https://api.github.com/users/jqlang/repos" \
  | jq '[.[] | {name: .name, stars: .stargazers_count}] | sort_by(-.stars)'

sort_by(-.stars) は負数でソートすることで降順にしています。

JSON 配列を CSV に変換

ユーザーリストの JSON をヘッダ付き CSV に変換します。

echo '[
  {"name":"Alice","age":30,"city":"Tokyo"},
  {"name":"Bob","age":25,"city":"Osaka"}
]' | jq -r '
  ["name","age","city"],
  (.[] | [.name, .age, .city])
  | @csv
'
"name","age","city"
"Alice",30,"Tokyo"
"Bob",25,"Osaka"

複数の JSON を集計(slurp)

JSON Lines 形式のログから特定フィールドを集計します。

cat access.log | jq -rs '
  map(select(.status >= 400))
  | group_by(.status)
  | map({status: .[0].status, count: length})
'

-rs-r(raw output)と -s(slurp)を組み合わせたもので、複数の JSON 行をまとめて読み込みます。

条件フィルタ:特定条件の要素だけ抽出

商品リストから在庫あり(stock > 0)かつ価格が 1000 円以下の商品を名前順で取得します。

echo '[
  {"name":"商品A","price":800,"stock":5},
  {"name":"商品B","price":1200,"stock":0},
  {"name":"商品C","price":500,"stock":10},
  {"name":"商品D","price":950,"stock":0}
]' | jq '[.[] | select(.stock > 0 and .price <= 1000)] | sort_by(.name)'
[
  {"name": "商品A", "price": 800, "stock": 5},
  {"name": "商品C", "price": 500, "stock": 10}
]
フィルタを先に試してから本番に使う

jq には公式のオンライン実行環境(https://jqlang.github.io/jq/tutorial/ のページや https://jqplay.org/ など)があります。複雑なフィルタを書くときは先に小さなサンプルで動作確認してから本番データに適用するのが安全です。

フィルタ早見表

よく使うフィルタ・関数をまとめます。

フィルタ / 関数意味
.入力をそのまま出力(整形)jq '.'
.fooフィールド取得.name
.a.bネストしたフィールド.user.city
.[0]配列のインデックス.[0] / .[-1]
.[]配列の全要素を展開.[] | .name
map(f)全要素に変換を適用map(. * 2)
select(f)条件で絞り込みmap(select(.age >= 20))
sort_by(f)フィールドでソートsort_by(.name)
group_by(f)フィールドでグループ化group_by(.type)
unique重複除去[.[].tag] | unique
add要素の集約map(.score) | add
length要素数・文字数.items | length
to_entriesオブジェクト → key/value 配列to_entries | map(.key)
from_entrieskey/value 配列 → オブジェクトfrom_entries
has(key)キーの存在確認has("name")
contains(b)包含確認contains({"role":"admin"})
test(regex)正規表現マッチselect(.email | test("@example"))
@csvCSV 形式に変換[.name, .age] | @csv
@tsvTSV 形式に変換[.name, .age] | @tsv
join(str)配列を文字列に結合join(", ")
split(str)文字列を配列に分割split(",")
ascii_downcase英字を小文字にascii_downcase

代替ツール

jq の互換ツールとして gojq(Go 実装)があります。jq とほぼ同じ構文で動作し、整数演算の精度が改善されているなどの特徴があります。Go でシングルバイナリとして配布されるため、環境によっては導入しやすいケースもあります。

jq 本体で十分な場合がほとんどですが、長整数を扱う処理で精度の問題に当たったときなどに選択肢として覚えておくとよいでしょう。

まとめ

jq は覚えるフィルタの数こそ多いですが、基本パターンは「. で整形」「.field で抽出」「| map() | select() で変換・絞り込み」の繰り返しです。まず curl のレスポンスを jq に渡して整形することから始め、少しずつフィルタを組み合わせていくと自然と身についていきます。

公式マニュアルは読み物として丁寧に書かれているので、一度通読しておくと引き出しが増えます。


出典

共有: X でシェア はてブ

編集・検証

よるほろぐ編集部

Linux・コンテナ・CLI・開発環境の実践ガイドを編集・検証します。

編集方針を読む