使用jq从pipe道分隔的键和bash中的值创buildJSON

我想从bash中的string创build一个json对象。 string如下。

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。 我想这个string格式为json。

 { "CONTAINER":"nginx_container", "CPU%":"0.02%", .... } 

我以前用过jq命令,好像在这种情况下应该可以正常工作,但是我还没有find一个好的解决scheme。 除了硬编码variables名称和使用sed或awk编制索引。 然后从头开始创build一个json。 任何build议,将不胜感激。 谢谢。

条件

对于以下所有内容,假定您的内容位于名为s的shellvariables中:

 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,从而使得inputstream的整个input可以被inputinputs读取 – 前者读取一行,后者读取所有剩余的行。 ( -R ,对于原始input,会导致读取文本行而不是JSON对象)。
  • [$keys, $vals] | transpose[] [$keys, $vals] | transpose[] ,我们正在生成[key, value]对(用Python术语,压缩两个列表)。
  • 使用{key:.[0],value:.[1]} ,我们将每个[key, value]对变为{"key": key, "value": value}
  • 使用from_entries ,我们将这些对组合成包含这些键和值的对象。

什么(壳辅助)

这将使用比上述更老的jq ,对于本地jq解决scheme可能更难解决的场景,这是一个容易采用的方法:

 { 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' \ <<<'{}' 

…传递每个键和值的带外(这样它被视为一个文字string而不是parsing为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\"" } 

…而不理解JSONstring的实现可能很容易发生:

 { "key ending in a backslash\": "value "with quotes"" } 
 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" } 

这是一个使用-R-s选项以及transpose的解决scheme:

  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%" ...