0Pricing
DevOps Bootcamp · レッスン

jqパイプラインによるJSONのフィルタリングと選択

jqのセレクター、パイプ、selectフィルターを使って、入れ子になったオブジェクトや配列を操作します。

「jqパイプラインによるJSONのフィルタリングと選択」はCoddyKit上の無料DevOps Bootcampレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDevOps Bootcamp学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DevOps Bootcampコースには全4レッスンが含まれています。

jqとは何か、なぜ使うのか

jqは、JSONデータの解析、フィルタリング、変換を行うための、軽量で強力なコマンドラインツールです。JSON版のsedともいえる存在で、JSONをパイプで渡すと、構造化された出力が返ります。

  • ほとんどのLinuxディストリビューションにプリインストールされているか、apt install jq / brew install jqでインストールできます
  • curlやcatなどのツールと、シェルパイプラインでシームレスに連携できます
  • フィルタリング、マッピング、畳み込み、形式変換をサポートしています

基本的な呼び出し方は、jq '<filter>' file.json、またはcat file.json | jq '<filter>'のようにパイプで渡す方法です。フィルター.(ドット)は恒等フィルターで、ドキュメント全体を見やすく整形して出力します。

# Pretty-print a JSON file
jq '.' data.json

# Or pipe from curl
curl -s https://api.github.com/users/torvalds | jq '.'

ドット記法でオブジェクトのフィールドを選択する

JSONオブジェクトのフィールドにアクセスするには、ドット記法.fieldNameを使います。セレクターを連結して、ネストしたオブジェクトをたどることもできます。

  • .name — 最上位のフィールド
  • .address.city — ネストしたフィールド
  • ."field-with-dash" — 特殊文字を含むフィールドには引用符が必要

フィールドが存在しない場合、jqはエラーにせずnullを返します。そのため、オプションのフィールドに対する追加のnullチェックを行わずに、安全にスクリプトで使用できます。

# Given: {"name":"Alice","address":{"city":"Berlin","zip":"10115"}}
echo '{"name":"Alice","address":{"city":"Berlin","zip":"10115"}}' | jq '.name'
# Output: "Alice"

echo '{"name":"Alice","address":{"city":"Berlin","zip":"10115"}}' | jq '.address.city'
# Output: "Berlin"

配列要素へのアクセスと反復

JSON配列には角かっこ記法でアクセスします。jqのインデックスはゼロから始まります。

  • .items[0] — 最初の要素
  • .items[-1] — 最後の要素
  • .items[1:3] — スライス(インデックス1以上3未満)
  • .items[] — 配列を展開し、各要素を個別の値として出力します(これがイテレーターです)

イテレーター[]はjqのパイプラインの基本です。後続のフィルターを各要素に個別に適用できます。

# Given an array of users
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Carol"}]' | jq '.[0]'
# Output: {"name":"Alice"}

# Iterate all elements and extract .name from each
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Carol"}]' | jq '.[].name'
# Output:
# "Alice"
# "Bob"
# "Carol"

パイプ演算子でjqパイプラインを構築する

シェルのパイプ|と同じように、jqにも独自の内部パイプ演算子があります。あるフィルターの出力を、次のフィルターの入力として渡します。

  • jq '.users[] | .name' — ユーザーを反復し、それぞれから名前を抽出します
  • jq '.data | .items[] | .id' — dataへ移動し、itemsを展開して、idを抽出します

jq式のパイプを使うと、複雑な変換を段階的に組み立てられます。各段階は、イテレーターが生成した複数の値も含め、直前の段階が生成した値を受け取ります。

重要なポイント:イテレーターがN個の値を生成すると、後続の各フィルターは値ごとに1回、合計N回実行されます。

# Nested pipeline: navigate -> iterate -> extract
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq '.users[] | .name'
# Output:
# "Alice"
# "Bob"

# Chain more stages
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq '.users[] | .age'
# Output:
# 30
# 25

select()でフィルタリングする

select(condition)フィルターは、条件が真の場合にのみ値を通過させ、それ以外の場合は何も出力しません。jqにおけるgrep、またはSQLのWHEREに相当します。

  • select(.age > 18) — ageが18より大きいオブジェクトを残します
  • select(.status == "active") — 等価性を確認します
  • select(.name | startswith("A")) — 文字列に対するネストしたテスト

イテレーターとselectを組み合わせると、配列をフィルタリングできます。.items[] | select(.active)は、.activeが真とみなされる要素だけを出力します。

# Filter array elements by a condition
echo '[{"name":"Alice","age":30},{"name":"Bob","age":17},{"name":"Carol","age":25}]' \
  | jq '.[] | select(.age >= 18) | .name'
# Output:
# "Alice"
# "Carol"

# Filter by string equality
echo '[{"name":"Alice","role":"admin"},{"name":"Bob","role":"user"}]' \
  | jq '.[] | select(.role == "admin") | .name'
# Output: "Alice"

{}と[]でオブジェクトと配列を再構成する

jqでは、{}で新しいオブジェクトを、[]で新しい配列を構築して、データの形を変えられます。

  • {name: .name, city: .address.city} — フィールドを選択し、名前を変えて新しいオブジェクトにまとめます
  • [.items[] | .id] — 反復した値を配列に収集します
  • 短縮記法:{name, age}は{name: .name, age: .age}と同じです

パイプラインを[...]で囲むことを配列構築と呼びます。値のストリームではなく、出力としてJSON配列が必要な場合に不可欠です。

# Reshape: keep only selected fields
echo '[{"id":1,"name":"Alice","password":"secret"},{"id":2,"name":"Bob","password":"secret"}]' \
  | jq '[.[] | {id, name}]'
# Output:
# [
#   {"id": 1, "name": "Alice"},
#   {"id": 2, "name": "Bob"}
# ]

# Collect filtered names into an array
echo '[{"name":"Alice","active":true},{"name":"Bob","active":false}]' \
  | jq '[.[] | select(.active) | .name]'
# Output: ["Alice"]

ネストした配列と再帰下降を扱う

実際のJSONは、深くネストしていることがよくあります。jqには、深い階層を移動するための2つのツールがあります。

  • .a.b.c — 構造が分かっている場合の明示的なパス
  • .. | .fieldName? — 再帰下降。ツリー内のすべてのノードをたどり、キーが存在する場所の値を出力します

?(try)演算子は、対象のノードにフィールドが存在しない場合のエラーを抑制します。異なる構造が混在するツリーで再帰下降を使う際に重要です。

大きなドキュメントでは、再帰下降の使用は控えめにしてください。すべてのノードを訪問するため、遅くなる可能性があります。構造が予測可能なら、明示的なパスを優先してください。

# Explicit deep path
echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c'
# Output: 42

# Recursive descent: find all "id" values anywhere in the tree
echo '{"users":[{"id":1,"profile":{"id":99}},{"id":2}]}' \
  | jq '.. | .id?'
# Output:
# 1
# 99
# 2

実践例:curlのAPIレスポンスを解析する

jqの最も一般的な用途の一つは、curlで取得したREST APIのレスポンスを解析することです。curl -s(サイレントモード)とjqのパイプラインを組み合わせると、スクリプトで扱いやすい、すっきりしたデータ抽出ができます。

  • 単一の値を抽出する:curl -s URL | jq '.field'
  • 集計表を作成する:配列を反復し、必要なフィールドだけでオブジェクトを再構成します
  • -r(raw output)を使って文字列値を囲む引用符を取り除きます。シェル変数への代入に不可欠です

ヒント:jqの出力をシェル変数として使う場合や、別のコマンドへパイプする場合は、必ず-rを追加してください。

#!/usr/bin/env bash
# Fetch GitHub repo info and extract specific fields
REPO="torvalds/linux"
RESPONSE=$(curl -s "https://api.github.com/repos/${REPO}")

# Extract fields
STARS=$(echo "$RESPONSE" | jq -r '.stargazers_count')
LANG=$(echo  "$RESPONSE" | jq -r '.language')
DESC=$(echo  "$RESPONSE" | jq -r '.description')

echo "Stars : $STARS"
echo "Lang  : $LANG"
echo "Desc  : $DESC"

map()とmap_values()を使う

jqには、コレクションを変換するための便利な高階関数が2つあります。

  • map(f) — 配列のすべての要素にフィルターfを適用し、新しい配列を返します。[.[] | f]と同じです。
  • map_values(f) — オブジェクトまたは配列のすべての値にfを適用し、キーやインデックスを保持します。

これらを使うと、パイプラインを手作業で[]で囲むよりも読みやすくなります。配列のまま保持したい変換では、jqらしい慣用的な書き方です。

# map: extract a field from each element
echo '[{"name":"Alice","score":95},{"name":"Bob","score":80}]' \
  | jq 'map(.name)'
# Output: ["Alice", "Bob"]

# map with select: filter + transform in one step
echo '[{"name":"Alice","score":95},{"name":"Bob","score":60}]' \
  | jq 'map(select(.score >= 70) | .name)'
# Output: ["Alice"]

# map_values: multiply every value in an object by 2
echo '{"a":1,"b":2,"c":3}' | jq 'map_values(. * 2)'
# Output: {"a":2,"b":4,"c":6}

//でオプションのフィールドとデフォルト値を扱う

外部ソースのJSONデータには、一貫性がないことがよくあります。フィールドが欠落していたり、nullになっていたりする場合があります。jqには、デフォルト値を指定するための代替演算子//(ダブルスラッシュ)があります。

  • .nickname // "anonymous" — .nicknameがnullまたはfalseでなければそれを使い、そうでなければ"anonymous"を使います
  • .count // 0 — 数値のデフォルト値
  • selectと組み合わせる:select((.status // "inactive") == "active")

これはシェルでの${VAR:-default}に相当する書き方よりはるかに簡潔で、長いパイプラインの中にも自然に組み込めます。

# Provide defaults for missing/null fields
echo '[{"name":"Alice","role":"admin"},{"name":"Bob"}]' \
  | jq '[.[] | {name, role: (.role // "user")}]'
# Output:
# [
#   {"name": "Alice", "role": "admin"},
#   {"name": "Bob",   "role": "user"}
# ]

# Numeric default
echo '{"items":[1,2,3]}' | jq '.total // 0'
# Output: 0

実践スクリプト:JSONログパーサー

構造化JSONロギングは、現代のシステムで標準的に使われています。ここでは、改行区切りJSON形式のログファイルを読み込み、エラーのエントリをフィルタリングして、人間が読みやすい概要に整形する、実用的なスクリプトを紹介します。

使用している主なパターン:

  • -c(コンパクト出力) — 1行に1つのJSONオブジェクトを出力します。シェルループへのパイプに便利です
  • --arg name value — シェル変数をjqの文字列引数として注入します
  • ログレベルのフィルタリングにselectを使います
  • echoに適した生の文字列出力に-rを使います
#!/usr/bin/env bash
# Parse newline-delimited JSON logs and report ERRORs
# Each log line: {"level":"ERROR","msg":"...","ts":"2024-01-15T10:23:00Z","svc":"auth"}

LOG_FILE="/var/log/app/app.log"
LEVEL="ERROR"

echo "=== $LEVEL entries in $LOG_FILE ==="

jq -r --arg lvl "$LEVEL" \
  'select(.level == $lvl) | "[\(.ts)] [\(.svc)] \(.msg)"' \
  "$LOG_FILE"

# Count errors per service
echo ""
echo "=== Error count by service ==="
jq -r --arg lvl "$LEVEL" \
  'select(.level == $lvl) | .svc' "$LOG_FILE" \
  | sort | uniq -c | sort -rn

理解度チェック:jq select() の動作

jq パイプライン内で select() がどのように動作するか、理解度を確認しましょう。

次のコマンドがあるとします。

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

出力はどうなるでしょうか。

レッスンのまとめ:JSON フィルタリングのための jq パイプライン

コマンドラインから JSON を移動・フィルタリングするための、jq の基本ツールを学びました。

  • ドット記法(.field、.a.b.c)でオブジェクトからフィールドを選択します
  • 配列アクセス(.[0]、.[])で配列の要素をインデックス指定したり、反復処理したりします
  • パイプ演算子(|)でフィルターを連結します。各ステージは前のステージから渡されたすべての値を処理します
  • select(cond) で値をフィルタリングし、条件が truthy である値だけを通過させます
  • オブジェクト/配列の構築({}、[]、map())でデータを新しい構造に組み替えます
  • 代替演算子(//)で、null または存在しないフィールドにデフォルト値を指定します
  • -r フラグでシェル変数への代入時に引用符を除去し、--arg でシェル変数を安全に注入します
  • 再帰的下降(.. | .field?)で、パスが不明な場合でも深くネストされたツリーを検索します

これらの基本要素を使えば、JSON API のレスポンス、ログファイル、設定を、ターミナルから離れることなく、スクリプトに必要なデータへ正確に変換できます。

よくある質問

「jqパイプラインによるJSONのフィルタリングと選択」レッスンは無料ですか?

はい。「jqパイプラインによるJSONのフィルタリングと選択」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DevOps Bootcampコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DevOps Bootcampコースには全4レッスンが含まれています。

「jqパイプラインによるJSONのフィルタリングと選択」で何を学びますか?

jqのセレクター、パイプ、selectフィルターを使って、入れ子になったオブジェクトや配列を操作します。 ブラウザで直接実行するハンズオンコードでDevOps Bootcampを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

DevOps Bootcampを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのDevOps Bootcampは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「jqパイプラインによるJSONのフィルタリングと選択」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このDevOps Bootcampレッスンでコードを書いて実行できますか?

はい。すべてのDevOps Bootcampレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. jqパイプラインによるJSONのフィルタリングと選択
  2. jqによるJSONオブジェクトの変換と構築
  3. curlとjqを組み合わせたREST APIの利用
  4. yqによるYAML設定ファイルの編集
← DevOps Bootcampに戻る