Масив JSON для баш змінних за допомогою jq


19

У мене такий масив JSON:

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

Я хочу переглядати цей масив за допомогою jq, щоб я міг встановити ключ кожного елемента як ім'я змінної та значення як його значення.

Приклад:

  • URL = "example.com"
  • AUTHOR = "Джон Доу"
  • СТВОРЕНО = "22.10.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щось або щось там, але я не можу покласти свій палець на це.

Відповіді:


29

Ваша оригінальна версія не evalзможе, тому що ім'я автора має пробіли - це трактується як виконання команди Doeзі змінною середовища, AUTHORвстановленою на John. Також практично ніколи не потрібно підключатися jqдо себе - внутрішні трубопроводи та потік даних можуть з'єднувати різні фільтри разом.

Ви можете зробити набагато простішу версію програми jq:

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

який виводить:

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

Там немає необхідності в протягом 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 --версію безпечною, порівнявши ключ $ зі списком дозволених імен змінних.
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

0

Мені дуже подобається пропозиція @Michel. Іноді ви можете просто витягнути значення змінних для виконання завдання на цьому конкретному сервері за допомогою BASH. Отже, бажані змінні знають. Цей підхід - це спосіб уникнути або декількох викликів jq, щоб встановити значення для змінної або навіть використовувати оператор читання з декількома змінними, де деякі можуть бути дійсними та порожніми, що призводить до зміни значення (це була моя проблема)

мій попередній підхід, що веде за собою, призведе до помилки зсуву значення, якщо .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] | це дуже часто, якщо джерело, якщо з сервера RESTFULL API, що використовує 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 )"  

Це давнє питання, але я відчув спільне використання, оскільки його важко було знайти

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.