Преобразование объекта JSON в ассоциативный массив Bash

у меня есть сценарий Bash. Он получает данные в JSON. Мне нужно преобразовать массив JSON в массив Bash.

пример

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

в Bash я хочу получить такое значение, как это echo ${arr[SOMETHING]}.

5 ответов


если вы хотите ключ и значение, и на основе как преобразовать объект json в формат key=value в JQ, вы можете сделать:

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

в более общем виде вы можете хранить значения в массиве myarray[key] = value вот так, просто предоставив jq до while С while ... 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

контекст: этот ответ был написан, чтобы реагировать на заголовок вопроса, который больше не существует..


вопрос OP фактически описывает объекты, vs массивы.

чтобы быть уверенным, что мы помогаем другим людям, которые приходят, которые на самом деле глядя на помощь с массивами JSON, хотя, стоит их явно покрывать.


для безопасного случая, когда строки не могут содержать newlines (и когда используется bash 4.0 или новее), это работает:

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

для поддержки старых версий bash и строк с новыми строками мы получаем немного больше, используя поток с нулевыми разделителями для чтения из 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")')

хотя на этот вопрос ответили, Я не смог полностью насытить мой требования из опубликованного ответа. Вот немного записи, которая поможет любому Баш-новички.

предвидение

объявление базового ассоциативного массива

#!/bin/bash

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

вы также можете использовать кавычки (', ") в районе declaration, его keys, и values.

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

в зависимости от вашего варианта цитаты, вам может потребоваться избежать вашей строки.

использование косвенного доступа к ключу и значению в ассоциативном массиве

function 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

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

  # print associative array
}

трубопровод вашего JSON в JQ

#!/usr/bin/env bash

function 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;
  arrayAsString=$(cat "$json" | jq --raw-output "${filter}")
  local -A "$arrayAsString"

  # print associative array
}

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

#!/usr/bin/env bash

function 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 может выполнить).

это может быть сведено к следующим

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

  # print associative array
}

@fedorqui

если вы хотите ключ и значение, и на основе как преобразовать объект json в формат key=value в JQ, вы можете сделать:

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

в a более общий способ, вы можете хранить значения в массиве myarray[key] = value как это, просто предоставив jq до while С while ... 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.

каждое решение действительно и в зависимости от вашего варианта использования, один может быть более полезным потом другой.


вот как это можно сделать рекурсивно:

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

назвать это:

get_settings "$SETTINGS_JSON"

массив будет доступен следующим образом:

${SETTINGS[grandparent:parent:child]}

чтобы эффективно загрузить объект 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 может выполнить).