Tablica JSON do bash zmiennych przy użyciu jq

19

Mam tablicę JSON taką:

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

Szukam iteracji po tej tablicy za pomocą jq, dzięki czemu mogę ustawić klucz każdego elementu jako nazwę zmiennej i wartość jako wartość.

Przykład:

  • URL = „example.com”
  • AUTOR = „John Doe”
  • UTWORZONY = „10/22/2017”

To, co mam do tej pory, iteruje tablicę, ale tworzy ciąg:

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

Które wyjścia:

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

Chcę użyć tych zmiennych w dalszej części skryptu:

echo ${URL}

Ale w tej chwili przypomina to pusty wynik. Zgaduję, że potrzebuję evalczegoś w tym stylu, ale nie mogę położyć na tym palca.

EHerman
źródło

Odpowiedzi:

29

Twoja oryginalna wersja nie będzie w evalstanie, ponieważ w nazwie autora znajdują się spacje - byłoby to interpretowane jako uruchamianie polecenia Doeze zmienną środowiskową AUTHORustawioną na John. Praktycznie nigdy nie ma też potrzeby tworzenia potoków jqdo siebie - wewnętrzny potok i przepływ danych mogą łączyć ze sobą różne filtry.

Możesz stworzyć znacznie prostszą wersję programu jq:

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

które wyjścia:

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

Nie ma potrzeby map: .[]zajmuje się przenoszeniem każdego obiektu w tablicy przez resztę potoku jako osobnego elementu , więc wszystko po ostatnim |jest stosowane do każdego z osobna. Na koniec po prostu montujemy prawidłowy ciąg przypisania powłoki ze zwykłą +konkatenacją, w tym cudzysłowy wokół wartości.

Wszystkie potoki mają tutaj znaczenie - bez nich otrzymujesz dość nieprzydatne komunikaty o błędach, w których części programu są oceniane w subtelnie różnych kontekstach.

Ciąg ten jest evalw stanie tak długo jak bohaterów `, $, nowej linii i zerowy nie pojawiają się w danych:

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

Jak zawsze podczas korzystania eval, pamiętaj, aby ufać otrzymywanym danym, ponieważ jeśli są one złośliwe lub mają nieoczekiwany format, wszystko może pójść nie tak.

Michael Homer
źródło
13

Opierając się na odpowiedzi @Michael Homer, możesz evalcałkowicie uniknąć potencjalnie niebezpiecznego, wczytując dane do tablicy asocjacyjnej.

Na przykład jeśli dane JSON znajdują się w pliku o nazwie 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]}'"

Wynik:

$ ./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'
cas
źródło
1
Możesz także pośrednio przydzielić zadanie samemu declare -- “$key=$value”i mieć $AUTHORetc działające jak w oryginale, bez tablicy. Jest to nadal bezpieczniejsze niż ewaluacja, choć zmiana PATHlub coś jest nadal możliwe, więc mniej niż ta wersja.
Michael Homer
1
tak, tablica ładnie izoluje zmienne w wybranym przez Ciebie pojemniku - nie ma szans na przypadkowe / złośliwe zakłócenie ważnych zmiennych środowiskowych. możesz zwiększyć declare --bezpieczeństwo swojej wersji, porównując klucz $ z listą dozwolonych nazw zmiennych.
cas
1

Właśnie zdałem sobie sprawę, że mogę przeglądać wyniki i sprawdzać każdą iterację:

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

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

Pozwala mi na:

echo ${AUTHOR}
# outputs John Doe
EHerman
źródło
0

Bardzo podoba mi się sugestia @Michel. Czasami naprawdę możesz po prostu wyodrębnić niektóre wartości zmiennych, aby wykonać zadanie na tym konkretnym serwerze za pomocą BASH. Zatem pożądane zmienne są znane. Takie zastosowanie jest sposobem uniknięcia lub wielokrotnych wywołań jq w celu ustawienia wartości dla zmiennej, a nawet użycia instrukcji read z wieloma zmiennymi, w których niektóre mogą być poprawne i puste, co prowadzi do zmiany wartości (to był mój problem)

moje poprzednie podejście prowadzi do błędu przesunięcia wartości, jeśli .svID [] .ID = "" ( sv otrzyma wartość slotID

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

Jeśli pobrałeś obiekt za pomocą curl, oto moje podejście do zmiany nazwy niektórych zmiennych na przyjazną nazwę, aby wyodrębnić dane z tablic danych

użycie eval i filtrów rozwiąże problem z jedną linią i wytworzy zmienne o pożądanej nazwie

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 )"  

Zaletą w tym przypadku jest fakt, że w pierwszym kroku będzie filtrować, zmieniać nazwę, formatować wszystkie pożądane zmienne. Zauważ, że tam jest. [0] | jest to bardzo powszechne, jeśli źródło pochodzi z serwera API RESTFULL używającego GET, dane odpowiedzi jako:

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

Jeśli dane nie pochodzą z tablicy, tj. jest przedmiotem takim jak:

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

wystarczy usunąć początkowy indeks:

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 )"  

To stare pytanie, ale czułem dzielenie się, ponieważ trudno było je znaleźć

Thiago Conrado
źródło