まえがき
systemd-nspawnコンテナでコンテナのメタ情報等をJSON形式で取得できた。
保守しやすいようにJSON形式をTSV形式に変換できないかと考えた。
本文
仕様
配列にオブジェクトが複数件登録されているシンプルなJSONデータを標準入力として受け取り、ヘッダ行と明細行に分けて加工し、TSV形式で標準出力に出力する仕様
バージョン
$ jq --version
jq-1.6
配列にオブジェクトが複数件が格納されている場合
インプット
$ cat test.json
[
{
"machine": "vir-ubuntu-18-04-001",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.210\nfe80::ec3e:dff:febc:4d55"
},
{
"machine": "vir-ubuntu-18-04-002",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.211\nfe80::18d7:6aff:fef7:799c"
},
{
"machine": "vir-ubuntu-18-04-003",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.212\nfe80::12:92ff:fe02:380e"
},
{
"machine": "vir-ubuntu-18-04-004",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.213\nfe80::c878:ecff:fe6b:f8cb"
},
{
"machine": "vir-ubuntu-18-04-005",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.214\nfe80::f0ad:efff:fec2:ddce"
}
]
コマンド
ワンライナー
$ cat test.json | jq -r '. as $in | $in | map(keys)|reduce .[] as $item([];. + $item)|unique as $column_list | $in | ($column_list|join("\t")),map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[]'
ワンライナーじゃない
ユニークなカラム一覧を取得したあと、この一覧リストをヘッダ行の処理と明細行の処理に渡している。
明細行の処理でtojsonしているのは改行文字等がふくまれてくる可能性があるので、基本tojsonしておくといいと考えた。
cat test.json | \
jq -r '. as $in |
$in | map(keys)|reduce .[] as $item([];. + $item)|unique as $column_list |
$in |
($column_list|join("\t"))
,map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[]'
アウトプット
addresses class machine os service version
"192.168.1.210\nfe80::ec3e:dff:febc:4d55" "container" "vir-ubuntu-18-04-001" "ubuntu" "systemd-nspawn" "18.04"
"192.168.1.211\nfe80::18d7:6aff:fef7:799c" "container" "vir-ubuntu-18-04-002" "ubuntu" "systemd-nspawn" "18.04"
"192.168.1.212\nfe80::12:92ff:fe02:380e" "container" "vir-ubuntu-18-04-003" "ubuntu" "systemd-nspawn" "18.04"
"192.168.1.213\nfe80::c878:ecff:fe6b:f8cb" "container" "vir-ubuntu-18-04-004" "ubuntu" "systemd-nspawn" "18.04"
"192.168.1.214\nfe80::f0ad:efff:fec2:ddce" "container" "vir-ubuntu-18-04-005" "ubuntu" "systemd-nspawn" "18.04"
配列にオブジェクトが単一件が格納されている場合
インプット
$ cat c.json
[
{
"machine": "vir-ubuntu-18-04-001",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.210\nfe80::ec3e:dff:febc:4d55"
}
]
コマンド
配列にオブジェクトが複数件が格納されている場合と同じ
アウトプット
addresses class machine os service version
"192.168.1.210\nfe80::ec3e:dff:febc:4d55" "container" "vir-ubuntu-18-04-001" "ubuntu" "systemd-nspawn" "18.04"
オブジェクト単一の場合
インプット
$ cat b.json
{
"ID": 1,
"なまえ": "ぽるこ",
"役割": "ぶた"
}
コマンド
ワンライナー
cat b.json | jq -r '. as $in | (if type == "array" then map(keys) else [keys] end) as $key_list | $key_list | reduce .[] as $item([];. + $item)|unique as $column_list | ($column_list|join("\t")),(if ($in|type) == "array" then ($in|map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[]) else ($column_list|map(. as $column|($in[$column]|tojson))|join("\t")) end)'
ワンライナーじゃない
標準入力が配列の場合とオブジェクトの場合で処理を分岐するように上記2つのパタンのコマンドから少し改良した。
cat b.json | \
jq -r '. as $in |
(if type == "array" then map(keys) else [keys] end) as $key_list |
$key_list | reduce .[] as $item([];. + $item)|unique as $column_list |
($column_list|join("\t"))
,(
if ($in|type) == "array" then ($in|map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[])
else ($column_list|map(. as $column|($in[$column]|tojson))|join("\t"))
end
)'
アウトプット
ID なまえ 役割
1 "ぽるこ" "ぶた"
汎用コマンド化
以下の内容をjson2tblなどの名前でファイル保存して実行権限与えてパスを通せば、ハンディに扱える。
# !/usr/bin/env bash
usage(){
cat <<EOS
Usage:
CMD: echo test.json | ${0##*/}
or
CMD: echo '{"ID":1,"なまえ":"ぽるこ","役割":"ぶた"}' | ${0##*/}
EOS
exit 0
}
main(){
ARGS=($(cat -));
CNT=${#ARGS[@]}
if [ -z $CNT ] ;then
usage
fi
if [[ 1 -ne $CNT ]] ;then
usage
fi
if [ -f ${ARGS[0]} ] ;then
cat ${ARGS[0]} | \
sed '/^$/d' | \
jq -r '. as $in | (if type == "array" then map(keys) else [keys] end) as $key_list | $key_list | reduce .[] as $item([];. + $item)|unique as $column_list | ($column_list|join("\t")),(if ($in|type) == "array" then ($in|map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[]) else ($column_list|map(. as $column|($in[$column]|tojson))|join("\t")) end)'
else
echo ${ARGS[0]} | \
jq -r '. as $in | (if type == "array" then map(keys) else [keys] end) as $key_list | $key_list | reduce .[] as $item([];. + $item)|unique as $column_list | ($column_list|join("\t")),(if ($in|type) == "array" then ($in|map(. as $entry |($column_list|map(. as $column|($entry[$column]|tojson))|join("\t")))[]) else ($column_list|map(. as $column|($in[$column]|tojson))|join("\t")) end)'
fi
}
[ -p /dev/stdin ] && cat - | main
[ -p /dev/stdin ] || echo -ne "$@" | main
使用感
$ cat a.json
[
{
"ID": 1,
"なまえ": "ぽるこ",
"役割": "ぶた"
},
{
"ID": 2,
"なまえ": "ジーナ",
"役割": "恋人"
}
]
# ファイル名を渡さないとエラー
$ cat a.json | json2tbl-jq
Usage:
CMD: echo test.json | json2tbl-jq
or
CMD: echo '{"ID":1,"なまえ":"ぽるこ","役割":"ぶた"}' | json2tbl-jq
$ echo a.json | json2tbl-jq
ID なまえ 役割
1 "ぽるこ" "ぶた"
2 "ジーナ" "恋人"
$ echo '{"ID":1,"なまえ":"ぽるこ","役割":"ぶた"}' | json2tbl-jq
ID なまえ 役割
1 "ぽるこ" "ぶた"
$ cat c.json
[
{
"machine": "vir-ubuntu-18-04-001",
"class": "container",
"service": "systemd-nspawn",
"os": "ubuntu",
"version": "18.04",
"addresses": "192.168.1.210\nfe80::ec3e:dff:febc:4d55"
}
]
$ echo c.json | json2tbl-jq
addresses class machine os service version
"192.168.1.210\nfe80::ec3e:dff:febc:4d55" "container" "vir-ubuntu-18-04-001" "ubuntu" "systemd-nspawn" "18.04"
こういうのも便利
$ cat sample.json
{
"Fruits": [
{
"Name": "Apple",
"Quantity": 3,
"Price": 100
},
{
"Name": "Orange",
"Quantity": 15,
"Price": 110
},
{
"Name": "Mango",
"Quantity": 100,
"Price": 90
},
{
"Name": "Banana",
"Quantity": 6,
"Price": 100
},
{
"Name": "Kiwifruit",
"Quantity": 40,
"Price": 50
}
]
}
$ cat sample.json | jq '."Fruits"'
[
{
"Name": "Apple",
"Quantity": 3,
"Price": 100
},
{
"Name": "Orange",
"Quantity": 15,
"Price": 110
},
{
"Name": "Mango",
"Quantity": 100,
"Price": 90
},
{
"Name": "Banana",
"Quantity": 6,
"Price": 100
},
{
"Name": "Kiwifruit",
"Quantity": 40,
"Price": 50
}
]
$ cat sample.json | jq -c '."Fruits"'
[{"Name":"Apple","Quantity":3,"Price":100},{"Name":"Orange","Quantity":15,"Price":110},{"Name":"Mango","Quantity":100,"Price":90},{"Name":"Banana","Quantity":6,"Price":100},{"Name":"Kiwifruit","Quantity":40,"Price":50}]
$ cat sample.json | jq -c '."Fruits"' | json2tbl-jq
Name Price Quantity
"Apple" 100 3
"Orange" 110 15
"Mango" 90 100
"Banana" 100 6
"Kiwifruit" 50 40
あとがき
jq便利