将 JSON 对象转换为 Bash 关联数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26717277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:36:58  来源:igfitidea点击:

Converting a JSON object into a Bash associative array

arraysjsonbashjq

提问by Evgenii

I have a Bash script. It gets data in JSON. I need to convert the JSON array to a Bash array.

我有一个 Bash 脚本。它以 JSON 格式获取数据。我需要将 JSON 数组转换为 Bash 数组。

Example

例子

{
  "SALUTATION": "Hello world",
  "SOMETHING": "bla bla bla Mr. Freeman"
}

In Bash I want to get a value like this echo ${arr[SOMETHING]}.

在 Bash 中,我想获得这样的值echo ${arr[SOMETHING]}

回答by fedorqui 'SO stop harming'

If you want key and value, and based on How do i convert a json object to key=value format in JQ, you can do:

如果您想要键和值,并且基于如何将 json 对象转换为 JQ 中的键=值格式,您可以执行以下操作:

$ jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" file
SALUTATION=Hello world
SOMETHING=bla bla bla Mr. Freeman

In a more general way, you can store the values into an array myarray[key] = valuelike this, just by providing jqto the whilewith the while ... do; ... done < <(command)syntax:

在更普遍的方式,你可以将值存储到一个数组myarray[key] = value这个样子,只是通过提供jqwhilewhile ... do; ... done < <(command)语法:

declare -A myarray
while IFS="=" read -r key value
do
    myarray[$key]="$value"
done < <(jq -r 'to_entries|map("(.key)=(.value)")|.[]' file)

And then you can loop through the values like this:

然后你可以循环遍历这样的值:

for key in "${!myarray[@]}"
do
    echo "$key = ${myarray[$key]}"
done

For this given input, it returns:

对于这个给定的输入,它返回:

SALUTATION = Hello world
SOMETHING = bla bla bla Mr. Freeman

回答by Charles Duffy

Context: This answer was written to be responsive to a question title which no longer exists..

上下文:编写此答案是为了响应不再存在的问题标题。.



The OP's question actually describes objects, vs arrays.

OP 的问题实际上描述了对象与数组。

To be sure that we help other people coming in who are actuallylooking for help with JSON arrays, though, it's worth covering them explicitly.

不过,为了确保我们帮助其他真正寻求 JSON 数组帮助的人,值得明确地介绍他们。



For the safe-ish case where strings can't contain newlines (and when bash 4.0 or newer is in use), this works:

对于字符串不能包含换行符的安全情况(以及使用 bash 4.0 或更新版本时),这有效:

str='["Hello world", "bla bla bla Mr. Freeman"]'
readarray -t array <<<"$(jq -r '.[]' <<<"$str")"

To support older versions of bash, and strings with newlines, we get a bit fancier, using a NUL-delimited stream to read from jq:

为了支持旧版本的 bash 和带换行符的字符串,我们变得更有趣,使用 NUL 分隔的流来读取jq

str='["Hello world", "bla bla bla Mr. Freeman", "this is\ntwo lines"]'
array=( )
while IFS= read -r -d '' line; do
  array+=( "$line" )
done < <(jq -j '.[] | (. + "\u0000")')

回答by Sid

Although this question is answered, I wasn't able to fully satiate my requirements from the posted answer. Here is a little write up that'll help any bash-newcomers.

虽然这个问题得到了回答,但我无法从发布的答案中完全满足我的要求。这里有一篇小文章,可以帮助任何 bash 新手。

Foreknowledge

预知

A basic associative array declaration

一个基本的关联数组声明

#!/bin/bash

declare -A associativeArray=([key1]=val1 [key2]=val2)

You can also use quotes (', ") around the declaration, its keys, and values.

您还可以在、 its和 周围使用引号 ( ', ") 。declarationkeysvalues

#!/bin/bash

declare -A 'associativeArray=([key1]=val1 [key2]=val2)'

And you can delimit each [key]=valuepair via space or newline.

您可以[key]=value通过空格或换行符分隔每一对。

#!/bin/bash

declare -A associativeArray([key1]=value1
  ['key2']=value2 [key3]='value3'
  ['key4']='value2'               ["key5"]="value3"


  ["key6"]='value4'
  ['key7']="value5"
)

Depending on your quote variation, you may need to escape your string.

根据您的报价变体,您可能需要对字符串进行转义。

Using Indirection to access both key and value in an associative array

使用间接访问关联数组中的键和值

example () {
  local -A associativeArray=([key1]=val1 [key2]=val2)

  # print associative array
  local key value
  for key in "${!associativeArray[@]}"; do
    value="${associativeArray["$key"]}"
    printf '%s = %s' "$key" "$value"
  done
}

Running the example function

运行示例函数

$ example
key2 = val2
key1 = val1

Knowing the aforementioned tidbits allows you to derive the following snippets:

了解上述花絮可以让您得出以下片段:



The following examples will all have the result as the example above

下面的例子都会有上面例子的结果

String evaluation

字符串评估

#!/usr/bin/env bash

example () {
  local arrayAsString='associativeArray=([key1]=val1 [key2]=val2)'
  local -A "$arrayAsString"

  # print associative array
}

Piping your JSON into JQ

将您的 JSON 传输到 JQ

#!/usr/bin/env bash
# Note: usage of single quotes instead of double quotes for the jq
#       filter. The former is preferred to avoid issues with shell 
#       substitution of quoted strings.
example () {
  # Given the following JSON
  local json='{ "key1": "val1", "key2": "val2" }'

  # filter using `map` && `reduce`
  local filter='to_entries | map("[\(.key)]=\(.value)") |
    reduce .[] as $item ("associativeArray=("; . + ($item|@sh) + " ") + ")"'

  # Declare and assign separately to avoid masking return values.
  local arrayAsString;
  # Note: no encompassing quotation (")
  arrayAsString=$(cat "$json" | jq --raw-output "${filter}")
  local -A "$arrayAsString"

  # print associative array
}

jq -n / --null-input option + --argfile && redirection

jq -n / --null-input 选项 + --argfile && 重定向

#!/usr/bin/env bash

example () {
  # /path/to/file.json contains the same json as the first two examples
  local filter filename='/path/to/file.json'

  # including bash variable name in reduction
  filter='to_entries | map("[\(.key | @sh)]=\(.value | @sh) ")
    | "associativeArray=(" + add + ")"'

  # using --argfile && --null-input
  local -A "$(jq --raw-output --null-input --argfile file "$filename" \
    "$filename | ${filter}")"

  # or for a more traceable declaration (using shellcheck or other) this
  # variation moves the variable name outside of the string

  # map definition && reduce replacement
  filter='[to_entries[]|"["+(.key|@sh)+"]="+(.value|@sh)]|"("+join(" ")+")"'

  # input redirection && --join-output
  local -A associativeArray=$(jq --join-output "${filter}" < "${filename}")

  # print associative array
}


Reviewing previous answers

回顾以前的答案

@Ján Lalinsky

@扬·拉林斯基

To load JSON object into a bash associative array efficiently (without using loops in bash), one can use tool 'jq', as follows.

# first, load the json text into a variable:
json='{"SALUTATION": "Hello world", "SOMETHING": "bla bla bla Mr. Freeman"}'

# then, prepare associative array, I use 'aa':
unset aa
declare -A aa

# use jq to produce text defining name:value pairs in the bash format
# using @sh to properly escape the values
aacontent=$(jq -r '. | to_entries | .[] | "[\"" + .key + "\"]=" + (.value | @sh)' <<< "$json")

# string containing whole definition of aa in bash
aadef="aa=($aacontent)"

# load the definition (because values may contain LF characters, aadef must be in double quotes)
eval "$aadef"

# now we can access the values like this: echo "${aa[SOMETHING]}"

Warning:this uses eval, which is dangerous if the json input is from unknown source (may contain malicious shell commands that eval may execute).

要有效地将 JSON 对象加载到 bash 关联数组中(不使用 bash 中的循环),可以使用工具“jq”,如下所示。

# first, load the json text into a variable:
json='{"SALUTATION": "Hello world", "SOMETHING": "bla bla bla Mr. Freeman"}'

# then, prepare associative array, I use 'aa':
unset aa
declare -A aa

# use jq to produce text defining name:value pairs in the bash format
# using @sh to properly escape the values
aacontent=$(jq -r '. | to_entries | .[] | "[\"" + .key + "\"]=" + (.value | @sh)' <<< "$json")

# string containing whole definition of aa in bash
aadef="aa=($aacontent)"

# load the definition (because values may contain LF characters, aadef must be in double quotes)
eval "$aadef"

# now we can access the values like this: echo "${aa[SOMETHING]}"

警告:这使用了 eval,如果 json 输入来自未知来源(可能包含 eval 可能执行的恶意 shell 命令),这很危险。

This could be reduced to the following

这可以简化为以下

example () {
  local json='{ "key1": "val1", "key2": "val2" }'
  local -A associativeArray=("$(jq -r '. | to_entries | .[] |
    "[\"" + .key + "\"]=" + (.value | @sh)' <<< "$json")")

  # print associative array
}

@fedorqui

@fedorqui

If you want key and value, and based on How do i convert a json object to key=value format in JQ, you can do:

$ jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" file
SALUTATION=Hello world
SOMETHING=bla bla bla Mr. Freeman

In a more general way, you can store the values into an array myarray[key] = valuelike this, just by providing jqto the whilewith the while ... do; ... done < <(command)syntax:

declare -A myarray
while IFS="=" read -r key value
do
    myarray[$key]="$value"
done < <(jq -r "to_entries|map(\"\(.key)=\(.value)\")|.[]" file)

And then you can loop through the values like this:

for key in "${!myarray[@]}"
do
    echo "$key = ${myarray[$key]}"
done

For this given input, it returns:

SALUTATION = Hello world
SOMETHING = bla bla bla Mr. Freeman

如果您想要键和值,并且基于如何将 json 对象转换为 JQ 中的键=值格式,您可以执行以下操作:

$ jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" file
SALUTATION=Hello world
SOMETHING=bla bla bla Mr. Freeman

在更普遍的方式,你可以将值存储到一个数组myarray[key] = value这个样子,只是通过提供jqwhilewhile ... do; ... done < <(command)语法:

declare -A myarray
while IFS="=" read -r key value
do
    myarray[$key]="$value"
done < <(jq -r "to_entries|map(\"\(.key)=\(.value)\")|.[]" file)

然后你可以循环遍历这样的值:

for key in "${!myarray[@]}"
do
    echo "$key = ${myarray[$key]}"
done

对于这个给定的输入,它返回:

SALUTATION = Hello world
SOMETHING = bla bla bla Mr. Freeman

The main difference between this solution and my own is looping through the array in bash or in jq.

这个解决方案和我自己的解决方案之间的主要区别是在 bash 或 jq 中循环遍历数组。

Each solution is valid and depending on your use case, one may be more useful then the other.

每个解决方案都是有效的,并且根据您的用例,一个可能比另一个更有用。

回答by HelpNeeder

This is how can it be done recursively:

这是如何递归完成的:

#!/bin/bash

SOURCE="$PWD"
SETTINGS_FILE="$SOURCE/settings.json"
SETTINGS_JSON=`cat "$SETTINGS_FILE"`

declare -A SETTINGS

function get_settings() {
    local PARAMS="$#"
    local JSON=`jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" <<< ""`
    local KEYS=''

    if [ $# -gt 1 ]; then
        KEYS=""
    fi

    while read -r PAIR; do
        local KEY=''

        if [ -z "$PAIR" ]; then
            break
        fi

        IFS== read PAIR_KEY PAIR_VALUE <<< "$PAIR"

        if [ -z "$KEYS" ]; then
            KEY="$PAIR_KEY"
        else
            KEY="$KEYS:$PAIR_KEY"
        fi

        if jq -e . >/dev/null 2>&1 <<< "$PAIR_VALUE"; then
            get_settings "$PAIR_VALUE" "$KEY"
        else
            SETTINGS["$KEY"]="$PAIR_VALUE"
        fi
    done <<< "$JSON"
}

To call it:

调用它:

get_settings "$SETTINGS_JSON"

The array will be accessed like so:

该数组将像这样访问:

${SETTINGS[grandparent:parent:child]}

回答by Ján Lalinsky

To load JSON object into a bash associative array efficiently (without using loops in bash), one can use tool 'jq', as follows.

要有效地将 JSON 对象加载到 bash 关联数组中(不使用 bash 中的循环),可以使用工具“jq”,如下所示。

# first, load the json text into a variable:
json='{"SALUTATION": "Hello world", "SOMETHING": "bla bla bla Mr. Freeman"}'

# then, prepare associative array, I use 'aa':
unset aa
declare -A aa

# use jq to produce text defining name:value pairs in the bash format
# using @sh to properly escape the values
aacontent=$(jq -r '. | to_entries | .[] | "[\"" + .key + "\"]=" + (.value | @sh)' <<< "$json")

# string containing whole definition of aa in bash
aadef="aa=($aacontent)"

# load the definition (because values may contain LF characters, aadef must be in double quotes)
eval "$aadef"

# now we can access the values like this: echo "${aa[SOMETHING]}"

Warning:this uses eval, which is dangerous if the json input is from unknown source (may contain malicious shell commands that eval may execute).

警告:这使用了 eval,如果 json 输入来自未知来源(可能包含 eval 可能执行的恶意 shell 命令),这很危险。