JSON массив для bash переменных с использованием jq

18

У меня есть массив JSON, например, так:

{
  "SITE_DATA": {
    "URL": "example.com",
    "AUTHOR": "John Doe",
    "CREATED": "10/22/2017"
  }
}

Я пытаюсь перебрать этот массив с помощью jq, чтобы я мог установить ключ каждого элемента в качестве имени переменной и значение в качестве ее значения.

Пример:

  • URL = "example.com"
  • AUTHOR = "Джон Доу"
  • СОЗДАНО = "10/22/2017"

То, что у меня есть, перебирает массив, но создает строку:

constants=$(cat ${1} | jq '.SITE_DATA' | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")

Какие выводы:

URL=example.com
AUTHOR=John Doe
CREATED=10/22/2017

Я хочу использовать эти переменные ниже в сценарии:

echo ${URL}

Но это отголосок пустой информации на данный момент. Я предполагаю, что мне нужно evalили что-то там, но, кажется, не могу указать на это.

EHerman
источник

Ответы:

28

Ваша оригинальная версия не будет в evalсостоянии, потому что имя автора содержит пробелы - это будет интерпретироваться как выполнение команды Doeс переменной окружения, AUTHORустановленной в John. Кроме того, практически никогда не возникает необходимость в конвейерной связи jq- внутренний трубопровод и поток данных могут соединять различные фильтры вместе.

Вы можете сделать гораздо более простую версию программы jq:

jq -r '.SITE_DATA | to_entries | .[] | .key + "=\"" + .value + "\""'

какие выводы:

URL="example.com"
AUTHOR="John Doe"
CREATED="10/22/2017"

Нет необходимости в a map: .[]имеет дело с переносом каждого объекта в массиве через остальную часть конвейера как отдельный элемент , поэтому все после последнего |применяется к каждому в отдельности. В конце мы просто собираем правильную строку назначения оболочки с обычной +конкатенацией, включая кавычки вокруг значения.

Здесь важны все каналы - без них вы получаете довольно бесполезные сообщения об ошибках, где части программы оцениваются в слегка различающихся контекстах.

Эта строка в evalсостоянии до тех пор , как символы `, $, символ новой строки и нуль не появляются в данных:

eval "$(jq -r '.SITE_DATA | to_entries | .[] | .key + "=\"" + .value + "\""' < data.json)"
echo "$AUTHOR"

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

Майкл Гомер
источник
13

Опираясь на ответ @Michael Homer, вы можете полностью избежать потенциально небезопасных eval, считывая данные в ассоциативный массив.

Например, если ваши данные JSON находятся в файле с именем file.json:

#!/bin/bash

typeset -A myarray

while IFS== read -r key value; do
    myarray["$key"]="$value"
done < <(jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + .value ' file.json)

# show the array definition
typeset -p myarray

# make use of the array variables
echo "URL = '${myarray[URL]}'"
echo "CREATED = '${myarray[CREATED]}'"
echo "AUTHOR = '${myarray[URL]}'"

Выход:

$ ./read-into-array.sh 
declare -A myarray=([CREATED]="10/22/2017" [AUTHOR]="John Doe" [URL]="example.com" )
URL = 'example.com'
CREATED = '10/22/2017'
AUTHOR = 'example.com'
саз
источник
1
Вы также можете косвенно присваивать присваивание declare -- “$key=$value”и $AUTHORработать так же, как в оригинале, без массива. Это все еще безопаснее, чем Eval, хотя изменение PATHили что-то еще возможно, так что меньше, чем в этой версии.
Майкл Гомер
1
да, массив красиво изолирует переменные в контейнер по вашему выбору - нет никаких шансов случайно / злонамеренно связываться с важными переменными среды. Вы можете сделать свою declare --версию безопасной, сравнив $ key со списком разрешенных имен переменных.
Cas
1

Просто понял, что я могу перебрать результаты и оценить каждую итерацию:

constants=$(cat ${1} | jq '.SITE_DATA' | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")

for key in ${constants}; do
  eval ${key}
done

Позволяет мне сделать:

echo ${AUTHOR}
# outputs John Doe
EHerman
источник
0

Мне очень нравится предложение @Michel. Иногда вы можете просто извлечь значение некоторых переменных, чтобы выполнить задачу на этом конкретном сервере, используя BASH. Итак, желаемые переменные известны. Использование этого подхода - это способ избежать или многократных обращений к jq для установки значения для переменной или даже к использованию оператора read с несколькими переменными, в которых некоторые могут быть допустимыми и пустыми, что приводит к сдвигу значения (это была моя проблема)

Мой предыдущий подход привел к ошибке сдвига значения, если .svID [] .ID = "" ( sv получит значение slotID

-rd '\n' getInfo sv slotID <<< $(jq -r '(.infoCMD // "no info"), (.svID[].ID // "none"), (._id // "eeeeee")' <<< $data)

Если вы загрузили объект с помощью curl, вот мой подход переименовать некоторые переменные в понятное имя для извлечения данных из массивов данных

использование eval и фильтров решит проблему с одной строкой и создаст переменные с нужным именем

eval "$(jq -r '.[0] | {varNameExactasJson, renamedVar1: .var1toBeRenamed, varfromArray: .array[0].var, varValueFilter: (.array[0].textVar|ascii_downcase)} | to_entries | .[] | .key + "=\"" + (.value | tostring) + "\""' <<< /path/to/file/with/object )"  

Преимущество в этом случае заключается в том, что он будет фильтровать, переименовывать, форматировать все нужные переменные на первом этапе. Заметьте, что там есть. [0] | это очень часто бывает, если источник if с сервера API RESTFULL, использующего GET, ответит как:

[{"varNameExactasJson":"this value", "var1toBeRenamed: 1500, ....}]

Если ваши данные не из массива, т.е. это объект как:

{"varNameExactasJson":"this value", "var1toBeRenamed: 1500, ....}

просто удалите начальный индекс:

eval "$(jq -r '{varNameExactasJson, renamedVar1: .var1toBeRenamed, varfromArray: .array[0].var, varValueFilter: (.array[0].textVar|ascii_downcase)} | to_entries | .[] | .key + "=\"" + (.value | tostring) + "\""' <<< /path/to/file/with/object )"  

Это старый вопрос, но я чувствовал, что делюсь, так как было трудно найти

Тьяго Конрадо
источник