私はbashの文字列からjsonオブジェクトを作成しようとしています。文字列は次のとおりです。
CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0
出力はdocker statsコマンドからのもので、私の最終目標はカスタムメトリックスをAWSクラウドウォッチに公開することです。この文字列をjsonとしてフォーマットしたいと思います。
{
"CONTAINER":"nginx_container",
"CPU%":"0.02%",
....
}
私は以前にjqコマンドを使用しましたが、この場合はうまく機能するようですが、まだ良い解決策を思い付くことができませんでした。変数名のハードコーディングとsedまたはawkを使用したインデックス作成以外。次に、jsonをゼロから作成します。任意の提案をいただければ幸いです。ありがとう。
以下のすべてについて、コンテンツはs
という名前のシェル変数にあると想定されています。
s='CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0'
# thanks to @JeffMercado and @chepner for refinements, see comments
jq -Rn '
( input | split("|") ) as $keys |
( inputs | split("|") ) as $vals |
[[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries
' <<<"$s"
これには、非常に新しい(おそらく1.5?)jq
が必要であり、コードの塊です。分解するには:
-n
を使用すると、jq
が単独でstdinを読み取れなくなり、入力ストリーム全体がinput
およびinputs
-前者から読み取れるようになります。単一の行を読み、後者は残りのすべての行を読みます。 (-R
、生入力の場合、JSONオブジェクトではなくテキスト行が読み取られます)。[$keys, $vals] | transpose[]
を使用して、[key, value]
ペアを生成しています(Python用語で、2つのリストを圧縮)。{key:.[0],value:.[1]}
を使用して、各[key, value]
ペアを{"key": key, "value": value}
の形式のオブジェクトにしていますfrom_entries
を使用して、これらのペアを組み合わせて、これらのキーと値を含むオブジェクトを作成します。これは、上記よりもかなり古いjq
で動作し、ネイティブjq
ソリューションの方が扱いにくい場合に簡単に採用できるアプローチです。
{
IFS='|' read -r -a keys # read first line into an array of strings
## read each subsequent line into an array named "values"
while IFS='|' read -r -a values; do
# setup: positional arguments to pass in literal variables, query with code
jq_args=( )
jq_query='.'
# copy values into the arguments, reference them from the generated code
for idx in "${!values[@]}"; do
[[ ${keys[$idx]} ]] || continue # skip values with no corresponding key
jq_args+=( --arg "key$idx" "${keys[$idx]}" )
jq_args+=( --arg "value$idx" "${values[$idx]}" )
jq_query+=" | .[\$key${idx}]=\$value${idx}"
done
# run the generated command
jq "${jq_args[@]}" "$jq_query" <<<'{}'
done
} <<<"$s"
上記から呼び出されたjq
コマンドは次のようになります。
jq --arg key0 'CONTAINER' \
--arg value0 'nginx_container' \
--arg key1 'CPU%' \
--arg value1 '0.0.2%' \
--arg key2 'MEMUSAGE/LIMIT' \
--arg value2 '25.09MiB/15.26GiB' \
'. | .[$key0]=$value0 | .[$key1]=$value1 | .[$key2]=$value2' \
<<<'{}'
...各キーと値を帯域外で渡し(JSONとして解析されるのではなくリテラル文字列として扱われる)、それらを個別に参照します。
上記のいずれかが放出します:
{
"CONTAINER": "nginx_container",
"CPU%": "0.02%",
"MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
"MEM%": "0.16%",
"NETI/O": "0B/0B",
"BLOCKI/O": "22.09MB/4.096kB",
"PIDS": "0"
}
要するに:出力として有効なJSONを生成することが保証されているため。
より単純なアプローチを破る例として、以下を検討してください。
s='key ending in a backslash\
value "with quotes"'
確かに、これらは予期しないシナリオですが、jq
はそれらの対処方法を知っています。
{
"key ending in a backslash\\": "value \"with quotes\""
}
...一方、JSON文字列を理解していなかった実装は、簡単に放出される可能性があります。
{
"key ending in a backslash\": "value "with quotes""
}
json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "nginx_container" "0.02%" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"
Jqを使用しませんが、値に引数と環境を使用できます。
CONTAINER=nginx_container json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "$CONTAINER" "$1" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"
-R
および-s
オプションと transpose
を使用するソリューションを次に示します。
split("\n") # [ "CONTAINER...", "nginx_container|0.02%...", ...]
| (.[0] | split("|")) as $keys # [ "CONTAINER", "CPU%", "MEMUSAGE/LIMIT", ... ]
| (.[1:][] | split("|")) # [ "nginx_container", "0.02%", ... ] [ ... ] ...
| select(length > 0) # (remove empty [] caused by trailing newline)
| [$keys, .] # [ ["CONTAINER", ...], ["nginx_container", ...] ] ...
| [ transpose[] | {(.[0]):.[1]} ] # [ {"CONTAINER": "nginx_container"}, ... ] ...
| add # {"CONTAINER": "nginx_container", "CPU%": "0.02%" ...
JSONSTR=""
declare -a JSONNAMES=()
declare -A JSONARRAY=()
LOOPNUM=0
cat ~/newfile | while IFS=: read CONTAINER CPU MEMUSE MEMPC NETIO BLKIO PIDS; do
if [[ "$LOOPNUM" = 0 ]]; then
JSONNAMES=("$CONTAINER" "$CPU" "$MEMUSE" "$MEMPC" "$NETIO" "$BLKIO" "$PIDS")
LOOPNUM=$(( LOOPNUM+1 ))
else
echo "{ \"${JSONNAMES[0]}\": \"${CONTAINER}\", \"${JSONNAMES[1]}\": \"${CPU}\", \"${JSONNAMES[2]}\": \"${MEMUSE}\", \"${JSONNAMES[3]}\": \"${MEMPC}\", \"${JSONNAMES[4]}\": \"${NETIO}\", \"${JSONNAMES[5]}\": \"${BLKIO}\", \"${JSONNAMES[6]}\": \"${PIDS}\" }"
fi
done
戻り値:
{ "CONTAINER": "nginx_container", "CPU%": "0.02%", "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB", "MEM%": "0.16%", "NETI/O": "0B/0B", "BLOCKI/O": "22.09MB/4.096kB", "PIDS": "0" }
そもそもJSONデータを提供するようにdockerに依頼できます
docker stats --format "{{json .}}"
詳細については、以下を参照してください: https://docs.docker.com/config/formatting/
私はこれが古い投稿であることを知っていますが、あなたが求めるツールはjo
と呼ばれています: https://github.com/jpmens/jo
すばやく簡単な例:
$ jo my_variable="simple"
{"my_variable":"simple"}
もう少し複雑
$ jo -p name=jo n=17 parser=false
{
"name": "jo",
"n": 17,
"parser": false
}
配列を追加する
$ jo -p name=jo n=17 parser=false my_array=$(jo -a {1..5})
{
"name": "jo",
"n": 17,
"parser": false,
"my_array": [
1,
2,
3,
4,
5
]
}
私はジョーでかなり複雑なものを作成しましたが、ナイスなことは、無効なjsonを作成する可能性を心配して独自のソリューションを展開することを心配する必要がないことです。
表形式のデータから始めている場合は、 sqawk のように表形式のデータでネイティブに機能するものを使用してjsonにしてから、さらにjqを使用する方が理にかなっていると思います。
echo 'CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0' \
| sqawk -FS '[|]' -RS '\n' -output json 'select * from a' header=1 \
| jq '.[] | with_entries(select(.key|test("^a.*")|not))'
{
"CONTAINER": "nginx_container",
"CPU%": "0.02%",
"MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
"MEM%": "0.16%",
"NETI/O": "0B/0B",
"BLOCKI/O": "22.09MB/4.096kB",
"PIDS": "0"
}
jq
がなければ、sqawk
は少し多すぎます:
[
{
"anr": "1",
"anf": "7",
"a0": "nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0",
"CONTAINER": "nginx_container",
"CPU%": "0.02%",
"MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
"MEM%": "0.16%",
"NETI/O": "0B/0B",
"BLOCKI/O": "22.09MB/4.096kB",
"PIDS": "0",
"a8": "",
"a9": "",
"a10": ""
}
]