从bash关联数组构造json哈希

时间:2021-03-01 15:41:21

I would like to convert an associative array in bash to a json hash/dict. I would prefer to use jq to do this as it is already a dependency and I can rely on it to produce well formed json. Could someone demonstrate how to achieve this?

我想将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 }'

4 个解决方案

#1


4  

There are many possibilities, but given that you already have written a bash for loop, you might like to begin with this variation of your script:

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

#!/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? // .)) })'

The result reflects the ordering of keys produced by the bash for loop:

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

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

In general, the approach based on feeding jq the key-value pairs, with one key on a line followed by the corresponding value on the next line, has much to recommend it. A generic solution following this general scheme, but using NUL as the "line-end" character, is given below.

一般来说,基于为jq键值对提供方法的方法,在一行上有一个键后跟下一行的相应值,有很多建议。遵循该一般方案的通用解决方案,但使用NUL作为“线端”字符,在下面给出。

Keys and Values as JSON Entities

To make the above more generic, it would be better to present the keys and values as JSON entities. In the present case, we could write:

为了使上述更通用,最好将键和值呈现为JSON实体。在目前的情况下,我们可以写:

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

Other Variations

JSON keys must be JSON strings, so it may take some work to ensure that the desired mapping from bash keys to JSON keys is implemented. Similar remarks apply to the mapping from bash array values to JSON values. One way to handle arbitrary bash keys would be to let jq do the conversion:

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

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

You could of course do the same thing with the bash array values, and let jq check whether the value can be converted to a number or to some other JSON type as desired (e.g. using fromjson? // .).

您当然可以使用bash数组值执行相同操作,并让jq检查该值是否可以根据需要转换为数字或其他JSON类型(例如,使用fromjson?//。)。

A Generic Solution

Here is a generic solution along the lines mentioned in the jq FAQ and advocated by @CharlesDuffy. It uses NUL as the delimiter when passing the bash keys and values to jq, and has the advantage of only requiring one call to jq. If desired, the filter fromjson? // . can be omitted or replaced by another one.

这是一个通用的解决方案,沿着jq常见问题解答中提到并由@CharlesDuffy提倡。它在将bash键和值传递给jq时使用NUL作为分隔符,并且具有仅需要一次调用jq的优点。如果需要,来自json的过滤器? //可以省略或替换为另一个。

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? // .)})'

Output:

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

#2


4  

This answer is from nico103 on freenode #jq:

这个答案来自freenode #jq上的nico103:

#!/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

#3


1  

This has been posted, and credited to nico103 on IRC, which is to say, me.

这已经发布,并且在IRC上记入了nico103,也就是说,我。

The thing that scares me, naturally, is that these associative array keys and values need quoting. Here's a start that requires some additional work to dequote keys and values:

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

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"}
$

So now all that's needed is a jq function that removes the quotes, which come in several flavors:

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

  • if the string starts with a single-quote (ksh) then it ends with a single quote and those need to be removed
  • 如果字符串以单引号(ksh)开头,则它以单引号结束,需要删除

  • if the string starts with a dollar sign and a single-quote and ends in a double-quote, then those need to be removed and internal backslash escapes need to be unescaped
  • 如果字符串以美元符号和单引号开头并以双引号结尾,那么需要删除那些并且内部反斜杠转义需要未转义

  • else leave as-is
  • 否则就这样离开

I leave this last iterm as an exercise for the reader.

我把这最后一个留给读者作为练习。

I should note that I'm using printf here as the iterator!

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

#4


0  

You can initialize a variable to an empty object {} and add the key/values {($key):$value} for each iteration, re-injecting the result in the same variable :

您可以将变量初始化为空对象{},并为每次迭代添加键/值{($ 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"

#1


4  

There are many possibilities, but given that you already have written a bash for loop, you might like to begin with this variation of your script:

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

#!/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? // .)) })'

The result reflects the ordering of keys produced by the bash for loop:

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

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

In general, the approach based on feeding jq the key-value pairs, with one key on a line followed by the corresponding value on the next line, has much to recommend it. A generic solution following this general scheme, but using NUL as the "line-end" character, is given below.

一般来说,基于为jq键值对提供方法的方法,在一行上有一个键后跟下一行的相应值,有很多建议。遵循该一般方案的通用解决方案,但使用NUL作为“线端”字符,在下面给出。

Keys and Values as JSON Entities

To make the above more generic, it would be better to present the keys and values as JSON entities. In the present case, we could write:

为了使上述更通用,最好将键和值呈现为JSON实体。在目前的情况下,我们可以写:

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

Other Variations

JSON keys must be JSON strings, so it may take some work to ensure that the desired mapping from bash keys to JSON keys is implemented. Similar remarks apply to the mapping from bash array values to JSON values. One way to handle arbitrary bash keys would be to let jq do the conversion:

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

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

You could of course do the same thing with the bash array values, and let jq check whether the value can be converted to a number or to some other JSON type as desired (e.g. using fromjson? // .).

您当然可以使用bash数组值执行相同操作,并让jq检查该值是否可以根据需要转换为数字或其他JSON类型(例如,使用fromjson?//。)。

A Generic Solution

Here is a generic solution along the lines mentioned in the jq FAQ and advocated by @CharlesDuffy. It uses NUL as the delimiter when passing the bash keys and values to jq, and has the advantage of only requiring one call to jq. If desired, the filter fromjson? // . can be omitted or replaced by another one.

这是一个通用的解决方案,沿着jq常见问题解答中提到并由@CharlesDuffy提倡。它在将bash键和值传递给jq时使用NUL作为分隔符,并且具有仅需要一次调用jq的优点。如果需要,来自json的过滤器? //可以省略或替换为另一个。

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? // .)})'

Output:

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

#2


4  

This answer is from nico103 on freenode #jq:

这个答案来自freenode #jq上的nico103:

#!/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

#3


1  

This has been posted, and credited to nico103 on IRC, which is to say, me.

这已经发布,并且在IRC上记入了nico103,也就是说,我。

The thing that scares me, naturally, is that these associative array keys and values need quoting. Here's a start that requires some additional work to dequote keys and values:

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

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"}
$

So now all that's needed is a jq function that removes the quotes, which come in several flavors:

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

  • if the string starts with a single-quote (ksh) then it ends with a single quote and those need to be removed
  • 如果字符串以单引号(ksh)开头,则它以单引号结束,需要删除

  • if the string starts with a dollar sign and a single-quote and ends in a double-quote, then those need to be removed and internal backslash escapes need to be unescaped
  • 如果字符串以美元符号和单引号开头并以双引号结尾,那么需要删除那些并且内部反斜杠转义需要未转义

  • else leave as-is
  • 否则就这样离开

I leave this last iterm as an exercise for the reader.

我把这最后一个留给读者作为练习。

I should note that I'm using printf here as the iterator!

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

#4


0  

You can initialize a variable to an empty object {} and add the key/values {($key):$value} for each iteration, re-injecting the result in the same variable :

您可以将变量初始化为空对象{},并为每次迭代添加键/值{($ 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"