jq 入門 - シェルで JSON を扱う
この記事で解決できること
curlや API 出力の JSON を シェルから読みやすい形 に整形できるselect/map/-rなど jq の必須語彙 を最短で身につく- パイプ運用で「クオートが付いてシェルに渡せない」「文字化けする」などの 定番事故を防ぐ型 が手に入る
結論(実務の型)
- とりあえず整形 →
jq . - 値だけ抜く →
jq -r '.field' - 配列を 1 行 1 JSON にバラす →
jq -c '.[]' - 条件で絞る →
jq '.[] | select(.status=="ok")' - 取得失敗時に止めない →
jq '.field // empty'
前提(対象環境)
- jq 1.6 以降(Ubuntu 20.04 以降の
apt install jqで十分。1.7 系の機能差は本文で都度言及) - 公式マニュアル: jq Manual
jq とは何か?
jq は JSON 専用のフィルタ言語兼コマンド。grep や awk で JSON を無理に切るより、構造を理解した上で値を抜けるため、API 連携・ログ集計・CI スクリプトで事故が減る。
- 標準入力 → フィルタ → 標準出力という Unix パイプの素直なモデル
- フィルタ式は 左から右へ 評価される(パイプと同じ感覚)
- 数値・文字列・配列・オブジェクト・null・boolean を扱える型システムを持つ
jq をどうインストールするか?
最短は OS のパッケージマネージャ。バージョン違いで挙動差があるため、スクリプトに組み込む場合は jq --version で揃えるのが安全。
# Ubuntu / Debian $ sudo apt update && sudo apt install jq # RHEL / Rocky / AlmaLinux $ sudo dnf install jq # macOS (Homebrew) $ brew install jq # バージョン確認 $ jq --version
古い CentOS 7 系では epel-release 経由になる。CI 用に静的バイナリが欲しいときは 公式 Releases の単一ファイルバイナリを /usr/local/bin/jq に置くのが速い。
基本フィルタの読み方は?
jq のフィルタは「入力に対する変換式」。何も書かなければ恒等関数(整形して返す)、.field でオブジェクトから値を取り、.[] で配列を展開する。
1. 整形して表示する
$ echo '{"name":"linny","age":3}' | jq .{
"name": "linny",
"age": 3
}
2. オブジェクトからキーで取り出す
$ echo '{"name":"linny","age":3}' | jq '.name'"linny"
3. 配列を 1 要素ずつバラす
$ echo '[{"id":1},{"id":2}]' | jq '.[]'{"id":1}
{"id":2}
シングルクォートで囲むのは シェル変数展開を抑える ため。ダブルクォートを使うと $ や が解釈されて事故る。jq の式は常に'...'` で囲うのが鉄則。
オブジェクトと配列をどう扱うか?
.[].field で配列内の各要素から値を抜き、, で複数式を並列に評価、| でフィルタを連結する。grep | awk の感覚そのまま。
配列の各要素から値を抜く
$ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \
| jq '.[].tag'"a" "b"
複数フィールドをタプルで取り出す
$ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \
| jq '.[] | {id, tag}'{"id":1,"tag":"a"}
{"id":2,"tag":"b"}
{id, tag} は {id: .id, tag: .tag} の省略記法。スネークケースなど予約語以外のキー に限る。
ネストの取り出し(安全演算子 ?)
$ echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c'
42
# 存在しないキーは null だが、配列添字エラーは ? で抑制
$ echo '{}' | jq '.a.b?.c?'
nullselect でどう絞り込むか?
select(条件) は 条件が真の値だけを通す フィルタ。配列を .[] で展開してから渡すのが定石。SQL の WHERE 句に近い役割を持つ。
等値で絞る
$ echo '[{"s":"ok"},{"s":"ng"},{"s":"ok"}]' \
| jq '.[] | select(.s=="ok")'{"s":"ok"}
{"s":"ok"}
数値比較・複合条件
$ jq '.[] | select(.score >= 80 and .active)'
$ jq '.[] | select(.tag=="a" or .tag=="b")'
$ jq '.[] | select(.tag | startswith("v1"))'キーの有無で絞る
$ echo '[{"a":1},{"b":2}]' \
| jq '.[] | select(has("a"))'{"a":1}
フィルタ後にフィールドを抜く型
jq '.[] | select(.status=="error") | .message'
「絞ってから取り出す」順序を守るとデバッグしやすい。最後に // empty を付けると null を出さずに済む。
map と length と keys で何ができるか?
map(f) は配列の各要素に f を適用し、length は長さ、keys はオブジェクトのキー一覧を返す。配列全体に処理を流す ときに使う。
map:配列を加工する
$ echo '[1,2,3]' | jq 'map(. * 10)'
[ 10, 20, 30 ]
map(f) は [.[] | f] と等価。.[] | f だと 1 要素ずつ流れて くるのに対し、map は 配列のまま 返るのが違い。
length:要素数を数える
$ echo '[{"id":1},{"id":2},{"id":3}]' | jq 'length'
3文字列なら文字数、オブジェクトならキー数、null なら 0 を返す。型ごとに意味が変わる点に注意。
keys:キー一覧を取り出す
$ echo '{"a":1,"c":3,"b":2}' | jq 'keys'[ "a", "b", "c" ]
keys は ソート済み、keys_unsorted は挿入順。再現性が必要なら keys の方が安全。
出力をどう整形するか?(-r / -c)
人間が読む整形は jq .、シェル変数に渡すなら -r(raw 出力)、grep や xargs に流すなら -c(compact)。出力モードの選び方で 8 割の事故が決まる。
-r:文字列のクオートを外して生で出す
$ echo '{"name":"linny"}' | jq '.name'
"linny"
$ echo '{"name":"linny"}' | jq -r '.name'
linnyシェルで NAME=$(...) のように受けるなら 必ず -r。"linny" を変数に入れると展開時にダブルクォート混入する。
-c:1 行 1 JSON で出す
$ echo '[{"id":1},{"id":2}]' | jq -c '.[]'{"id":1}
{"id":2}
-c は 行指向ツールとの連携用。while read line ループや xargs -I {} での再投入に向く。
tsv / csv 出力(@tsv / @csv)
$ echo '[{"id":1,"name":"a"},{"id":2,"name":"b"}]' \
| jq -r '.[] | [.id, .name] | @tsv'1 a 2 b
@csv は値をクオートし、@tsv はタブ区切り。配列に詰めてから渡す のが正しい使い方。
-r は 文字列のみ クオートを外す。数値・オブジェクトはそのまま JSON として出る。配列を行に展開したいなら .[] を必ず挟む。
実務でよく使うパターン
API レスポンスの整形、ログの集計、設定ファイル更新の 3 つは現場で頻出。型を覚えておけばコピペで使い回せる。
curl で取って必要なフィールドだけ抜く
$ curl -sS "https://api.example.com/users" \ | jq -r '.users[] | "\(.id)\t\(.name)"'
-sS は静かに(成功時無音、エラー時のみ表示)。"\(.id)\t\(.name)" の 文字列補間 で自由な区切り文字を作れる。
group_by で集計する
$ jq '[.[] | {status}] | group_by(.status) | map({status: .[0].status, count: length})'group_by(f) は 同じ f の結果でグループ化 した配列の配列を返す。map({key: ..., count: length}) でカウント表に整形する。
設定ファイルの一部を書き換える
# package.json の version を更新 $ jq '.version = "1.2.3"' package.json > package.json.tmp \ && mv package.json.tmp package.json
jq ... file > file は中身が消える。シェルが先に > で空ファイルを作るため。必ず一時ファイル経由 か sponge(moreutils)を使う。
# sponge を使う安全版 $ jq '.version = "1.2.3"' package.json | sponge package.json
配列に要素を追加する
$ echo '{"items":[1,2]}' | jq '.items += [3]'{
"items": [
1,
2,
3
]
}
|= は 再代入オペレータ、+= は 加算代入。.items |= map(. * 2) のように深い場所を一括変換できる。
よくある落とし穴は?
null の伝播・型エラー・引数渡しの 3 つで詰まる人が多い。エラーメッセージを読んでから検索する だけで解決時間が半分になる。
1. null をパイプに流して jq: error (at <stdin>:0): Cannot iterate over null
# .users が存在しない場合
$ echo '{}' | jq '.users[]'
jq: error (at <stdin>:1): Cannot iterate over null (null)対処:// [] でデフォルト値を与える。
$ echo '{}' | jq '.users // [] | .[]'
# 何も出力されない(正常終了)2. シェル変数を式に埋め込みたい
# NG: クオートが衝突する $ KEY="name" $ jq ".$KEY" file.json # シェル展開後に '."name"' になることがある # OK: --arg で明示的に渡す $ jq --arg key "$KEY" '.[$key]' file.json
--arg は 文字列として、--argjson は JSON として 値を渡す。数値や配列を渡したいなら --argjson。
3. 文字列が配列っぽく見えるのに .[] できない
$ echo '"abc"' | jq '.[]'
jq: error (at <stdin>:1): Cannot iterate over string ("abc")文字列は配列ではない。型を確認するには type フィルタ を使う。
$ echo '"abc"' | jq 'type' "string"
4. 終了コードを判定したい
jq -e は 出力が false か null のとき終了コード 1 を返す。条件分岐に組み込める。
$ echo '{"ok":false}' | jq -e '.ok' || echo "失敗"
false
失敗コピペ用:安全テンプレ
# 整形してページャに流す
jq . file.json | less
# 値を変数に受ける(必ず -r)
NAME=$(curl -sS "$URL" | jq -r '.name')
# 配列を 1 行 JSON にして xargs に流す
curl -sS "$URL" | jq -c '.users[]' \
| xargs -I {} sh -c 'echo "USER: {}"'
# null 安全に値を抜く
jq -r '.path.to.value // "DEFAULT"'