【问题标题】:Create JSON using jq from pipe-separated keys and values in bash使用 jq 从 bash 中管道分隔的键和值创建 JSON
【发布时间】:2016-08-09 21:13:12
【问题描述】:

我正在尝试从 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。任何建议,将不胜感激。谢谢。

【问题讨论】:

  • 我不认为 JQ 是这项工作的工具(它是 JSON 输入/JSON 输出)我最近做了类似的事情并最终使用了 RUBY CSV 和 JSON 模块(CSV 可以使用 | 作为分隔符) Python 也有类似的类
  • 有些人使用 awk 从分隔输入创建 JSON
  • @Jimmy,嗯? jq 绝对是完成这项工作的绝佳工具。
  • @Jimmy, ...并且 jq 不限于 JSON 格式。它可以读取原始字符串(请参阅 -R 选项),并且支持正则表达式(因此它可以解析您的任何语法看适合发送它)。
  • @Jimmy, ... 就此而言,也不限于 JSON 输出;当前版本还支持编写 CSV、% 编码的 URI、HTML、POSIX-sh 兼容的 shell 转义语法和 base64 编码的文字字符串。

标签: json bash docker jq


【解决方案1】:

先决条件

对于以下所有内容,假设您的内容位于名为 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 自行读取标准输入,从而使inputinputs 可以读取整个输入流——前者读取单行,后者读取读取所有剩余的行。 (-R,对于原始输入,会导致读取文本行而不是 JSON 对象)。
  • 使用[$keys, $vals] | transpose[],我们生成[key, value] 对(在Python 术语中,压缩两个列表)。
  • 使用{key:.[0],value:.[1]},我们将每个[key, value] 对变成{"key": key, "value": value} 形式的对象
  • 通过from_entries,我们将这些对组合成包含这些键和值的对象。

什么(shell 辅助)

这将适用于比上述更旧的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""
}

【讨论】:

  • 如果你使用transpose 会更容易,因为你已经有了键和值的数组。转置有效地将它们压缩在一起,这将使您能够相当轻松地构建对象。
  • @JeffMercado,我觉得我遗漏了一些应该很明显的东西——有没有比[$keys, $vals] | transpose | [ .[] | {"key": .[0], "value": .[1]} ] | from_entries 更有意义的成语?
  • 我不知道是否有一种惯用的方法来做到这一点,但我看到了很多方法可以实现。我个人喜欢使用from_entries[[$keys,$values] | transpose[] | {key:.[0],value:.[1]}] | from_entries。或者从对中创建对象并将它们相加:[[$keys,$values] | transpose[] | {(.[0]):.[1]}] | add。或使用reduce 分配值:reduce ([$keys,$values] | transpose[]) as $p ({}; .[$p[0]] = $p[1])
  • 哦——add 的使用是我不知道的一个技巧。闪亮的! :)
  • 这似乎有效:jq -Rn '(input|split("|")) as $keys | (inputs | split("|")) as $vals | [[$keys, $vals] | transpose [] ...。我还没有看到完全避免变量的方法,可能是由于评估过滤器的顺序。
【解决方案2】:

我知道这是一个旧帖子,但你寻找的工具叫做johttps://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。

【讨论】:

  • 是一个非常简单的创建JSON文件的工具,比jq的开销要小。
  • 遗憾的是,jo 不适用于 Amazon Linux。
  • 这看起来很优雅,但我无法让它与我的 xargs 标准输入一起工作。所以我已经恢复到一个助手 heredoc cat &lt;&lt;EOFecho -n
  • 您如何尝试将jo 与heredoc 一起使用?也许是一个关于您如何尝试使用它的单独问题?如果您决定这样做,请在此处发布链接... :)
【解决方案3】:

你可以先让 docker 给你 JSON 数据

docker stats --format "{{json .}}"

有关更多信息,请参阅:https://docs.docker.com/config/formatting/

【讨论】:

    【解决方案4】:
    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" }
    

    【讨论】:

    • 仅供参考——参见pubs.opengroup.org/onlinepubs/009695399/basedefs/… 的第四段,指定环境变量名称的约定:操作系统和外壳程序仅使用带有大写字符的名称,而小写名称是“为应用程序保留的”,并且可以保证应用程序可以在该空间中定义任何名称,而无需修改标准实用程序行为。此约定也会影响 shell 变量,因为它们共享一个命名空间:使用环境变量的名称作为 shell 变量会覆盖该环境变量,从而导致冲突。
    • 我会从循环中分离读取标题,这样您就可以避免 LOOPNUM 逻辑:... | { IFS=: read -a jsonnames; while IFS=: read ...; do echo ...; done; }
    • 顺便说一句,管道进入循环意味着您无法保留该循环退出后的状态[缺少带有 lastpipe 选项的全新 bash,或者这是默认行为的 shell,例如 @ 987654328@]。请参阅 BashFAQ #24 (mywiki.wooledge.org/BashFAQ/024) -- while ...; done &lt;newfile{ read header; while ...; done; } &lt;newfile,而不是 cat newfile | while ...; done,将避免该限制。
    【解决方案5】:

    这是一个使用-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%" ...
    

    【讨论】:

      【解决方案6】:

      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 和 environment。

      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"

      【讨论】:

        【解决方案7】:

        如果您从表格数据开始,我认为使用原生处理表格数据的东西更有意义,例如 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"
            }
        

        没有jqsqawk 有点过分了:

        [
          {
            "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": ""
          }
        ]
        

        【讨论】:

          猜你喜欢
          • 2019-02-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-21
          • 1970-01-01
          • 2021-02-12
          相关资源
          最近更新 更多