31

我有一个以 JSON 格式获取数据的 Bash 脚本,我希望能够将 JSON 转换为可访问的结构- 数组/列表/或其他易于解析嵌套数据的模型。

示例

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

我想获得如下值:echo ${arr[SOMETHING]}

[不同的方法也是可选的。]

4

7 回答 7

32

如果您想要键和值,并且基于How do i convert a json object to key=value format in JQ,您可以执行以下操作:

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

以更一般的方式,您可以将值存储到这样的数组中myarray[key] = value,只需提供with语法: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
于 2014-11-03T15:28:53.340 回答
14

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

预知

一个基本的关联数组声明

#!/bin/bash

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

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

#!/bin/bash

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

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

#!/bin/bash

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


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

根据您的报价变化,您可能需要转义您的字符串。

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

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
}

运行示例函数

$ example
key2 = val2
key1 = val1

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


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

字符串评估

#!/usr/bin/env bash

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

  # print associative array
}

将 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 选项 + --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
}

回顾以前的答案

@Ján Lalinský

为了有效地将 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 命令),这很危险。

这可以简化为以下

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

  # print associative array
}

@fedorqui

如果您想要键和值,并且基于How do i convert a json object to key=value format in JQ,您可以执行以下操作:

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

以更一般的方式,您可以将值存储到这样的数组中myarray[key] = value,只需提供with语法: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

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

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

于 2018-08-05T02:00:44.500 回答
10

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


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

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


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

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

为了支持旧版本的 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")')
于 2016-11-14T18:42:58.323 回答
4

这是如何递归完成的:

#!/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)\")|.[]" <<< "$1"`
    local KEYS=''

    if [ $# -gt 1 ]; then
        KEYS="$2"
    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"
}

调用它:

get_settings "$SETTINGS_JSON"

将像这样访问数组:

${SETTINGS[grandparent:parent:child]}
于 2017-10-31T01:23:47.500 回答
2

为了有效地将 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 命令),这很危险。

于 2018-05-05T09:18:42.717 回答
1

以@HelpNeeder 的解决方案为基础(顺便说一句)

他的解决方案并没有真正使用整数,所以我做了一些补充。扩展的条件检查数量,因此可以说牺牲了一些性能。

此版本适用于整数和浮点值。

SOURCE="$PWD"
SETTINGS_FILE="./test2.json"
SETTINGS_JSON=`cat "$SETTINGS_FILE"`
declare -A SETTINGS

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

    if [ $# -gt 1 ]; then
  
        KEYS="$2"
    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
                
              
                res=$(jq -e . 2>/dev/null <<< "$PAIR_VALUE")
                
                exitCode=$?
                check=`echo "$PAIR_VALUE" | grep -E ^\-?[0-9]*\.?[0-9]+$`
          # if [ "${res}" ] && [ $exitCode -eq "0" ] && [[ ! "${PAIR_VALUE}" == ?(-)+([0-9]) ]]  ALTERNATIVE, works only for integer (not floating point)
          if [ "${res}" ] && [ $exitCode -eq "0" ] && [[ "$check" == '' ]]
            then
                get_settings "$PAIR_VALUE" "$KEY"
               else
            SETTINGS["$KEY"]="$PAIR_VALUE"
        fi
           
       

    done <<< "$JSON"
}
get_settings "$SETTINGS_JSON"
于 2021-07-25T12:55:12.230 回答
0

解决方案:使用jq(它是一个轻量级且灵活的命令行 JSON 处理器。)。

在 bash 中,我宁愿将 JSONs 对象分配给一个变量并使用jq 它来访问和解析它的正确结果。它比用数组解析这个结构更方便,它具有多种功能和特性,例如访问嵌套和复杂对象、选择方法、内置运算符和函数、正则表达式支持、比较等......

示例

example='{"SALUTATION": "Hello world","SOMETHING": "bla bla bla Mr. Freeman"}'
echo $example | jq .SOMETHING

# output:
"bla bla bla Mr. Freeman"
于 2020-12-02T10:06:49.020 回答