ターミナルにおける構造化データの悩み
午前3時、2,400行におよぶKubernetesのデプロイメントファイルの中から特定のコンテナイメージのバージョンを探し出さなければならなかった時のことを覚えています。grepやawkを使おうとしましたが、YAMLのネスト構造のせいで悪夢のような作業になりました。インデントがわずかに変わるだけで、正規表現が完全に壊れてしまうのです。curlのレスポンスで返ってきた50KBもの整形されていない(minifyされた)JSONの壁を前にしたことがある人なら,そのフラストレーションがわかるはずです。
DevOpsのワークフローやバックエンドシステムはJSONとYAMLで動いています。AWS CLIの出力,Docker Composeファイル、GitHub Actionsなどでこれらを扱います。こうしたデータを標準的なテキストツールで管理するのは、フォークでスープを飲もうとするようなものです。単純に、道具が間違っているのです。
従来のツールが通用しない理由
grep、sed、awkといった標準的なユーティリティは「行指向」です。ファイルを改行で区切られた文字列の並びとして扱います。しかし、JSONやYAMLはツリー構造です。”status”という名前のキーが、異なるネストされたオブジェクト内に15回登場することもあります。grep "status"を実行すると、それらがどこに属しているかというコンテキストが一切ないまま、15行の出力が表示されるだけです。
軽量化(minify)されたファイルも障害となります。JSONパーサーは空白を気にしませんが、grepは気にします。1行のファイルと100行にインデントされたファイルは、アプリケーションにとっては同一ですが、手動のパーススクリプトにとっては別物です。このように構造を認識できないことが、スキーマがわずかに変化しただけで手動の正規表現が失敗する理由です。
適切な方法を選択する
これらのフォーマットを処理する必要がある場合、通常は3つの選択肢があります:
- 手動スクリプト (Python/Node.js):
json.load()などを使ってスクリプトを書くことができます。動作はしますが、たった一つの値を確認するために10行のコードを書くのは時間がかかり非効率です。 - 標準的なLinuxツール: 前述の通り、
grepやsedは脆弱です。複雑にネストされたデータでは失敗することがよくあります。 - 専用パーサー (jq および yq): これらのツールはデータの構文を理解しています。ファイルをフラットなテキストではなく、検索可能なオブジェクトとして扱います。
初期化に2秒かかるPythonスクリプトを書く代わりに、C言語で書かれたjqを使えば、10MBのJSONファイルを約0.1秒で処理できます。メモリが4GBしかない本番環境のUbuntuサーバーでは、この効率性が重要になります。これにより、CSSセレクターのような感覚の構文でデータをクエリできるようになります。
セットアップ
ほとんどのディストリビューションにはデフォルトで含まれていませんが、標準リポジトリに用意されています。UbuntuやDebianでは、インストールは数秒で完了します:
sudo apt update
sudo apt install jq -y
# yqについては、Mike Farah氏によるバージョンが標準的です
sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq
sudo chmod +x /usr/bin/yq
jqを使ったJSON処理
jqを使う最初のステップは、通常、データを読みやすくすることです。GitHub APIの呼び出しから整形されていないレスポンスを受け取った場合は、次を実行します:
curl -s https://api.github.com/repos/stedolan/jq/commits?per_page=1 | jq '.'
'.'フィルタは、最もシンプルなツールです。入力を受け取り、そのまま出力しますが、フォーマットを整え、色付けを行ってくれます。
特定の値の抽出
具体的に見ていきましょう。ユーザーのJSONオブジェクトがあり、メールアドレスだけが必要な場合は、ドット記法を使用します。これにより、階層を素早く掘り下げることができます:
# 入力: {"id": 1, "profile": {"email": "[email protected]"}}
echo '{"id": 1, "profile": {"email": "[email protected]"}}' | jq '.profile.email'
配列とフィルタリング
配列は少し複雑になりますが、jqならスマートに処理できます。現在「active」である全ユーザーの名前が必要だとしましょう:
# サンプル: [{"name": "Alice", "active": true}, {"name": "Bob", "active": false}]
cat users.json | jq '.[] | select(.active == true) | .name'
.[]演算子で配列を反復処理します。次にselect()で項目をフィルタリングし、.nameで最終的な値を取り出します。
yqを使ったYAML処理
yqは非常に似た構文を使用するため、YAMLへの切り替えも簡単です。docker-compose.ymlファイルをチェックして、サービスがどのイメージを使用しているかを確認するには、次のコマンドを実行します:
yq '.services[].image' docker-compose.yml
ファイルを直接編集する
編集機能こそが、yqが真に優れている点です。私はCI/CDパイプラインで、デプロイ前にイメージタグを更新するためにこれを使用しています。一時ファイルを作成する手間も省けます:
yq -i '.services.web.image = "my-app:v2.0.5"' docker-compose.yml
-iフラグはファイルを直接変更します。これは、ファイル内の他の場所にある無関係な文字列を誤って置換してしまう可能性があるsedを使用するよりも、はるかに安全です。
フォーマットをまたいだワークフロー
YAMLデータを持っていても、APIがJSONを要求する場合があります。これらのツールをパイプで繋ぐことで、そのギャップを埋めることができます。異なるフォーマットを一つのものとして扱えるようになります。
# YAMLをJSONに変換し、特定の値を抽出する
yq -o=json eval config.yaml | jq '.database.port'
ターミナルだけでは不十分な場合
ターミナルツールは効率的ですが、視覚的なインターフェースが役立つこともあります。同僚と複雑な構造をデバッグする場合や、乱雑なログからコピーしたスニペットを検証する場合などがそうです。そうしたケースでは、私はToolCraftを使用しています。
こちらのJSON Formatter & Validatorは、完全にブラウザ上で動作するため非常に優れています。クライアントサイドで処理されるため、本番データがサーバーに送信されることはありません。また、急いでいる時にyqのフラグを調べるよりも、YAML ↔ JSON Converterを使う方が早くフォーマットを変換できることもあります。
最後に
jqとyqを習得すれば、手動で検索する時間を何時間も節約できます。まずは基本的なキーの検索から始めましょう。慣れてきたら、map()やreduce()関数も調べてみてください。これらのツールを使えば、インフラを「コード」として正確に扱うことができるようになります。JSONをただのテキストとして扱うのをやめれば、コマンドラインの力は飛躍的に高まります。

