【问题标题】:Constructing a json hash from a bash associative array从 bash 关联数组构造 json 哈希
【发布时间】:2017-06-28 01:20:20
【问题描述】:

我想将 bash 中的关联数组转换为 json hash/dict。我更喜欢使用 jq 来执行此操作,因为它已经是一个依赖项,我可以依靠它来生成格式良好的 json。有人可以演示如何实现这一目标吗?

#!/bin/bash

declare -A dict=()

dict["foo"]=1
dict["bar"]=2
dict["baz"]=3

for i in "${!dict[@]}"
do
    echo "key  : $i"
    echo "value: ${dict[$i]}"
done

echo 'desired output using jq: { "foo": 1, "bar": 2, "baz": 3 }'

【问题讨论】:

    标签: json bash jq


    【解决方案1】:

    有很多可能性,但鉴于您已经编写了一个 bash for 循环,您可能希望从脚本的这个变体开始:

    #!/bin/bash
    # Requires bash with associative arrays
    declare -A dict
    
    dict["foo"]=1
    dict["bar"]=2
    dict["baz"]=3
    
    for i in "${!dict[@]}"
    do
        echo "$i" 
        echo "${dict[$i]}"
    done |
    jq -n -R 'reduce inputs as $i ({}; . + { ($i): (input|(tonumber? // .)) })'
    

    结果反映了 bash for 循环产生的键的顺序:

    {
      "bar": 2,
      "baz": 3,
      "foo": 1
    }
    

    一般来说,基于为 jq 提供键值对的方法,即一行中的一个键和下一行中的相应值,有很多值得推荐的地方。下面给出了一个遵循这个通用方案但使用 NUL 作为“行尾”字符的通用解决方案。

    作为 JSON 实体的键和值

    为了使上述内容更通用,最好将键和值呈现为 JSON 实体。在本例中,我们可以这样写:

    for i in "${!dict[@]}"
    do
        echo "\"$i\""
        echo "${dict[$i]}"
    done | 
    jq -n 'reduce inputs as $i ({}; . + { ($i): input })'
    

    其他变体

    JSON 键必须是 JSON 字符串,因此可能需要一些工作来确保实现从 bash 键到 JSON 键的所需映射。类似的说明适用于从 bash 数组值到 JSON 值的映射。处理任意 bash 键的一种方法是让 jq 进行转换:

    printf "%s" "$i" | jq -Rs .
    

    您当然可以对 bash 数组值做同样的事情,并让 jq 检查该值是否可以根据需要转换为数字或其他 JSON 类型(例如,使用 fromjson? // .)。

    通用解决方案

    这是一个通用的解决方案,类似于 jq FAQ 中提到的并由@CharlesDuffy 提倡。它在将 bash 键和值传递给 jq 时使用 NUL 作为分隔符,并且具有只需要一次调用 jq 的优点。如果需要,过滤器fromjson? // . 可以省略或替换为另一个过滤器。

    declare -A dict=( [$'foo\naha']=$'a\nb' [bar]=2 [baz]=$'{"x":0}' )
    
    for key in "${!dict[@]}"; do
        printf '%s\0%s\0' "$key" "${dict[$key]}"
    done |
    jq -Rs '
      split("\u0000")
      | . as $a
      | reduce range(0; length/2) as $i 
          ({}; . + {($a[2*$i]): ($a[2*$i + 1]|fromjson? // .)})'
    

    输出:

    {
      "foo\naha": "a\nb",
      "bar": 2,
      "baz": {
        "x": 0
      }
    }
    

    【讨论】:

    • 好的,我喜欢这只会用换行符分隔的输入调用 jq 一次,这似乎是更通用的方法。我有点困惑 --null-input 和 --raw-input 如何交互,现在阅读关于 reduce 的文档。我认为这需要成为公认的答案。
    • 对于第二种解决方案,它被写为“针对当前情况”,它仅在值是整数时才有效。以防万一有人喜欢我并粘贴和剪切并尝试将其用于另一个案例。
    【解决方案2】:

    这个答案来自nico103freenode#jq

    #!/bin/bash
    
    declare -A dict=()
    
    dict["foo"]=1
    dict["bar"]=2
    dict["baz"]=3
    
    assoc2json() {
        declare -n v=$1
        printf '%s\0' "${!v[@]}" "${v[@]}" |
        jq -Rs 'split("\u0000") | . as $v | (length / 2) as $n | reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'
    }
    
    assoc2json dict
    

    【讨论】:

    • 当然,这不可避免地使用了一堆 bashism,但我建议使用符合 POSIX 的函数声明语法,以避免传播 shell-local 习惯用法。 assoc2json() {,没有 function 关键字,仅出于兼容性目的避免依赖 bash 支持的 ksh 语法。
    • 嗯。顺便说一句,这里让我有点担心的一件事是%q 引用了eval 风格的字符串by bash。我很确定,这里完成的处理将产生大量输入,这些输入不会返回原始文字值。另一方面,采用我的回答中采用的printf '%s\0' 方法非常简单,将其与此处更短的 jq 代码结合起来(仅采用字符串拆分位)...
    • 如果您看到我 (nico103) 在下面写下的答案,我的意思是将取消引用/取消转义留给读者。它应该很容易(尽管可能不是单行)。
    • printf '%s\0'很感兴趣!谢谢你的想法! jq 会将 NUL 转换为 \u0000,因此您必须对此进行拆分,这...您可以!所以单行现在更简单了:printf '%s\0' "${!arrayvar[@]}" "${arrayvar[@]}" | jq -Rs 'split("\u0000") | . as $v | (length / 2) as $n | reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'.
    • 根据上面 Charles Duffys 的建议更改为符合 POSIX 的函数声明语法。
    【解决方案3】:

    您可以将变量初始化为空对象{},并为每次迭代添加键/值{($key):$value},将结果重新注入同一变量中:

    #!/bin/bash
    
    declare -A dict=()
    
    dict["foo"]=1
    dict["bar"]=2
    dict["baz"]=3
    
    data='{}'
    
    for i in "${!dict[@]}"
    do
        data=$(jq -n --arg data "$data" \
                     --arg key "$i"     \
                     --arg value "${dict[$i]}" \
                     '$data | fromjson + { ($key) : ($value | tonumber) }')
    done
    
    echo "$data"
    

    【讨论】:

    • 不错的解决方案,我喜欢数据作为 jq arg 传递并迭代扩展的方式。
    【解决方案4】:

    这已经发布,并归功于 IRC 上的 nico103,也就是说,我。

    让我害怕的自然是这些关联数组的键和值需要引用。这是一个开始,需要一些额外的工作来取消引用键和值:

    function assoc2json {
        typeset -n v=$1
        printf '%q\n' "${!v[@]}" "${v[@]}" |
            jq -Rcn '[inputs] |
                    . as $v |
                    (length / 2) as $n |
                    reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'
    }
    
    
    $ assoc2json a
    {"foo\\ bar":"1","b":"bar\\ baz\\\"\\{\\}\\[\\]","c":"$'a\\nb'","d":"1"}
    $
    

    所以现在只需要一个删除引号的 jq 函数,它有多种形式:

    • 如果字符串以单引号 (ksh) 开头,则它以单引号结尾并且需要删除
    • 如果字符串以美元符号和单引号开头并以双引号结尾,则需要删除这些字符串并且内部反斜杠转义需要取消转义
    • 否则保持原样

    我将最后一个 iterm 留给读者作为练习。

    我应该注意我在这里使用printf 作为迭代器!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-27
      • 2011-03-09
      • 1970-01-01
      • 1970-01-01
      • 2012-09-02
      • 1970-01-01
      • 2010-11-21
      • 2015-08-09
      相关资源
      最近更新 更多