Linuxコマンド結果のJSON化は短い一行でも扱えますが、入力の種類や版を確認しないまま本番へ使うと誤判定を招きます。この記事の結論は「JSONを手作業の引用符連結で生成せず、まずipやjournalctlなどのネイティブJSON出力を使い、行指向データはjqの–raw-input、–slurp、–argで変換します。完成後はjq emptyで構文検証し、スキーマと数値型を確認します。」。コマンド自身がJSONを出せるかを最初に調べ、無い場合だけjqで安全に組み立てる場合を対象に、確認結果から次の行動を選べる形で解説します。
JSON化する前に入力schemaと出力単位を決める
JSON化対象はwildcardのまま渡さず、入力recordを列挙してschemaを確認します。jq、ip -j、journalctl -o jsonがshell builtin、cmdlet、外部programのどれかも確認し、別実装のoptionを混在させません。
- command –version またはパッケージ情報で実装と版を確認する
- 元コマンドの列が端末幅、ロケール、空白で変わらないか調べる
- 個人情報、コマンドライン引数、環境変数を出力対象から外す
- JSON Linesと単一のJSON配列のどちらを受け手が要求するか合意する
native JSONとjq変換を使い分ける
JSON対応コマンドをそのまま利用
ip -j address show | jq .
iproute2の-jは構造化された配列を返します。人間向けのip address出力を空白分割する方法より、インターフェース名や複数アドレスを失いにくい構成です。
key=valueを1行ずつ安全に変換
printf '%s\n' 'service=web' 'state=running' |
jq -Rn '[inputs | capture("^(?<key>[^=]+)=(?<value>.*)$")] | from_entries'
jq -Rは入力を文字列として受け、-nとinputsで明示的に読みます。値に引用符やバックスラッシュがあってもjqがエスケープするため、awkでJSON文字列を連結するより安全です。
シェル変数を–argで渡す
host=$(hostname)
status='ok'
jq -n --arg host "$host" --arg status "$status" \
'{host:$host,status:$status,checked_at:(now|todateiso8601)}'
–argは常にJSON文字列を作ります。数値として扱う値は入力検証後に–argjsonを使い、利用者入力を未検証のまま–argjsonへ渡しません。
生成物を保存前に検証
tmp=$(mktemp)
if ip -j link show | jq . >"$tmp" && jq -e 'type == "array"' "$tmp" >/dev/null; then
mv -- "$tmp" interfaces.json
else
printf '%s\n' 'JSON生成に失敗' >&2
fi
一時ファイルで生成と検証を終えてからmvするため、失敗途中のJSONで既存ファイルを上書きしません。既存interfaces.jsonは事前に世代付きで保管します。
broken JSONの発生源を順に切り分ける
- 末尾カンマをsedで消すだけの疑似JSON
- 空白区切りのpsやdfを固定列として解析する
- 数値をすべて文字列化する
- JSON Linesを配列と誤認する
- エラー出力を標準出力へ混ぜる
jqの終了statusとpayload型を別々に見る
JSONとして正しいことと、意味が正しいことは別です。jq typeで配列・オブジェクトを確認し、hasやlengthで必須項目を検証します。人間向けdf -hの値は単位付き文字列なので、監視計算へ使うならbytesを返す別の取得方法や明確な変換が必要です。プロセス一覧をJSON化するとコマンドラインにtokenが含まれる場合があり、取得項目の最小化もスキーマ設計に含めます。
既存JSONを途中生成物で上書きしない
JSON出力は読み取り中心ですが、保存先に既存の監視設定やAPI入力を指定すると誤配信を起こします。標準出力で少量を確認し、mktemp上でjq emptyとスキーマ検査を行い、既存ファイルをtimestamp付きで退避してから置換します。失敗時は一時ファイルを採用せず旧版を戻します。ログやユーザー一覧を外部サービスへ送る前に、UID、メール、パス、引数などの機密項目を削除し、転送先のアクセス権を確認します。
consumerまで通るschemaを再確認する
jq -e . file.jsonで構文と終了コードを確認し、jq ‘type, length’で最上位型と件数を記録します。元コマンドの件数とJSON要素数を突き合わせ、空入力、非ASCII、引用符、改行を含む値で再試験します。定期処理ではstderrを別ログへ分離し、生成失敗時に古い正常JSONを上書きしないこともテストします。
jq生成物を構文・型・件数で検収する
JSON出力を持つcommandはそのmodeを優先し、textから組み立てる場合はjq -n/–argで値を渡します。jqは有効な入力とfilter成功でstatus 0、parse errorで4、–exit-status使用時は最終値がfalse/nullなら1を返すため、保存前検証へ利用できます。
fixtureから一つのJSON objectを作る
host=$(hostname)
jq -n --arg host "$host" --arg state running '{host:$host,state:$state}' | jq -e 'type=="object" and has("host")'
valid・empty・invalid JSONを分離する
- schema適合:jq -e .が無出力errorなしでstatus 0を返し、後続parserがobject/arrayを読める
- 空入力・空配列:入力0件でも仕様上の空array []を出すのか、何も出さずstatus 1にするのかを先に固定する
- JSON生成error:途中行がJSONでない、quoteが壊れた、filterがcompileできない場合はstderrとnonzero statusで停止する
printfでquoteやbackslashを手作業escapeすると改行・tab・Unicodeで壊れます。複数JSON objectを連結したJSON Linesと、一つのarray documentも別formatなので、受け側の期待を確認します。
quoteと改行を含む値で壊れないか試す
printf '%s\n' '{"ok":true}' '{broken' | while IFS= read -r line; do printf '%s' "$line" | jq -e . >/dev/null || printf 'INVALID:%s\n' "$line"; done
空文字、改行入りvalue、backslash、日本語、0件、壊れた1行をtestし、jq -eのstatusと出力fileのbyte数を照合します。採用時はtemporary fileを同じdirectoryへ置き、parse成功後だけrenameします。
生成側のjqがstatus 0でも、受け手が期待するobjectとarrayを取り違えれば失敗です。保存前のjq -eと、実際のconsumerによる読み戻しの両方を完了条件にします。
JSON生成の境界値をconsumerと同じschemaで試す
fixtureには空行、=を含むvalue、引用符、backslash、改行、UTF-8文字、0件入力を含めます。jq -eではJSONとしてparseできることに加え、type、必須key、null可否、array件数をconsumer仕様として検証します。command本体のstatusとjqのstatusはPIPESTATUSまたは一時ファイルで分離し、上流失敗なのに空の有効JSONを保存する誤判定を防ぎます。保存前は同じdirectoryにtemporary fileを作り、parseとschemaの両方が合格した場合だけrenameします。

コメント