使用 bash 中以竖线分隔的键和值中的 jq 创建 JSON
Create JSON using jq from pipe-separated keys and values in bash
我正在尝试从 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 cloudwatch。我想将此字符串格式化为 json。
{
"CONTAINER":"nginx_container",
"CPU%":"0.02%",
....
}
我以前使用过 jq 命令,在这种情况下它似乎应该运行良好,但我还没有想出一个好的解决方案。除了使用 sed 或 awk 硬编码变量名称和索引。然后从头开始创建 json。任何建议,将不胜感激。谢谢。
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
Returns:
{ "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" }
先决条件
对于以下所有内容,假定您的内容位于名为 s
:
的 shell 变量中
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'
什么(现代jq)
# 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"
如何(现代jq)
这需要非常新的(可能是 1.5?)jq
才能工作,并且是一大堆代码。分解:
- 使用
-n
可以防止 jq
自己读取标准输入,从而使整个输入流可供 input
和 inputs
读取——前者读取一行,后者读取所有剩余行。 (-R
,对于原始输入,导致读取文本行而不是 JSON 对象)。
- 使用
[$keys, $vals] | transpose[]
,我们生成 [key, value]
对(在 Python 术语中,压缩两个列表)。
- 使用
{key:.[0],value:.[1]}
,我们将每个 [key, value]
对变成 {"key": key, "value": value}
形式的对象
- 使用
from_entries
,我们将这些对组合成包含这些键和值的对象。
什么(shell-辅助)
这将适用于比上述 jq
老得多的 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"
如何(shell-辅助)
上面调用的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""
}
这是一个使用 -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%" ...
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 但可以在值中使用 args 和环境。
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" "" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0")
echo "$json_string"
如果您从表格数据开始,我认为使用原生处理表格数据的东西更有意义,例如 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": ""
}
]
你可以要求docker第一时间给你JSON数据
docker stats --format "{{json .}}"
有关这方面的更多信息,请参阅:https://docs.docker.com/config/formatting/
我知道这是一个旧的 post,但您要找的工具叫做 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
]
}
我用 jo 做了一些非常复杂的东西,好处是你不必担心推出自己的解决方案而担心可能会使无效 json.
我正在尝试从 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 cloudwatch。我想将此字符串格式化为 json。
{
"CONTAINER":"nginx_container",
"CPU%":"0.02%",
....
}
我以前使用过 jq 命令,在这种情况下它似乎应该运行良好,但我还没有想出一个好的解决方案。除了使用 sed 或 awk 硬编码变量名称和索引。然后从头开始创建 json。任何建议,将不胜感激。谢谢。
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
Returns:
{ "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" }
先决条件
对于以下所有内容,假定您的内容位于名为 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'
什么(现代jq)
# 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"
如何(现代jq)
这需要非常新的(可能是 1.5?)jq
才能工作,并且是一大堆代码。分解:
- 使用
-n
可以防止jq
自己读取标准输入,从而使整个输入流可供input
和inputs
读取——前者读取一行,后者读取所有剩余行。 (-R
,对于原始输入,导致读取文本行而不是 JSON 对象)。 - 使用
[$keys, $vals] | transpose[]
,我们生成[key, value]
对(在 Python 术语中,压缩两个列表)。 - 使用
{key:.[0],value:.[1]}
,我们将每个[key, value]
对变成{"key": key, "value": value}
形式的对象
- 使用
from_entries
,我们将这些对组合成包含这些键和值的对象。
什么(shell-辅助)
这将适用于比上述 jq
老得多的 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"
如何(shell-辅助)
上面调用的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""
}
这是一个使用 -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%" ...
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 但可以在值中使用 args 和环境。
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" "" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0")
echo "$json_string"
如果您从表格数据开始,我认为使用原生处理表格数据的东西更有意义,例如 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": ""
}
]
你可以要求docker第一时间给你JSON数据
docker stats --format "{{json .}}"
有关这方面的更多信息,请参阅:https://docs.docker.com/config/formatting/
我知道这是一个旧的 post,但您要找的工具叫做 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
]
}
我用 jo 做了一些非常复杂的东西,好处是你不必担心推出自己的解决方案而担心可能会使无效 json.