Tableau JSON pour bash des variables en utilisant jq

19

J'ai un tableau JSON comme ceci:

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

Je cherche à parcourir ce tableau en utilisant jq afin que je puisse définir la clé de chaque élément comme nom de variable et la valeur comme sa valeur.

Exemple:

  • URL = "example.com"
  • AUTEUR = "John Doe"
  • CRÉÉ = "22/10/2017"

Ce que j'ai jusqu'à présent itère sur le tableau mais crée une chaîne:

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

Quelles sorties:

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

Je cherche à utiliser ces variables plus bas dans le script:

echo ${URL}

Mais cela fait écho à une sortie vide pour le moment. Je suppose que j'ai besoin d'un evalou de quelque chose là-dedans, mais je n'arrive pas à mettre le doigt dessus.

EHerman
la source

Réponses:

29

Votre version d'origine ne sera pas en evalmesure de le faire car le nom de l'auteur contient des espaces - elle serait interprétée comme exécutant une commande Doeavec la variable d'environnement AUTHORdéfinie sur John. Il n'est également pratiquement jamais nécessaire de se connecter jqà lui-même - la tuyauterie interne et le flux de données peuvent connecter différents filtres ensemble.

Vous pouvez créer une version beaucoup plus simple du programme jq:

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

qui génère:

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

Il n'y a pas besoin d'un map: .[]traite de la prise de chaque objet dans le tableau à travers le reste du pipeline en tant qu'élément séparé , donc tout ce qui se trouve après le dernier |est appliqué à chacun séparément. À la fin, nous assemblons simplement une chaîne d'affectation de shell valide avec une +concaténation ordinaire , y compris des guillemets autour de la valeur.

Tous les tuyaux sont importants ici - sans eux, vous obtenez des messages d'erreur assez inutiles, où des parties du programme sont évaluées dans des contextes subtilement différents.

Cette chaîne est en evalmesure aussi longtemps que les personnages `, $, et nul ne newline apparaissent pas dans les données:

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

Comme toujours lors de l'utilisation eval, veillez à faire confiance aux données que vous obtenez, car si elles sont malveillantes ou simplement dans un format inattendu, les choses pourraient très mal se passer.

Michael Homer
la source
13

En s'appuyant sur la réponse de @Michael Homer, vous pouvez éviter tout risque potentiellement dangereux evalen lisant les données dans un tableau associatif.

Par exemple, si vos données JSON se trouvent dans un fichier appelé 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]}'"

Production:

$ ./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
la source
1
Vous pouvez également indirectement l'affectation seule avec declare -- “$key=$value”et faire $AUTHORfonctionner etc. comme dans l'original, sans tableau. C'est toujours plus sûr qu'eval, bien que changer PATHou quelque chose soit encore possible donc moins que cette version.
Michael Homer
1
oui, le tableau isole joliment les variables dans un conteneur de votre choix - aucune chance de jouer accidentellement / malicieusement avec des variables d'environnement importantes. vous pouvez sécuriser votre declare --version en comparant $ key à une liste de noms de variables autorisés.
cas
1

Je viens de réaliser que je peux parcourir les résultats et évaluer chaque itération:

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

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

Me permet de faire:

echo ${AUTHOR}
# outputs John Doe
EHerman
la source
0

J'aime vraiment la suggestion @Michel. Parfois, vous pouvez vraiment extraire des valeurs de variables pour exécuter une tâche sur ce serveur spécifique à l'aide de BASH. Ainsi, les variables souhaitées sont connues. Cette utilisation de cette approche est le moyen d'éviter ou de multiples appels à jq pour définir une valeur par variable ou même d'utiliser l'instruction de lecture avec plusieurs variables dans lesquelles certaines peuvent être valides et vides, conduisant à un changement de valeur (c'était mon problème)

mon approche précédente qui conduit à une erreur de décalage de valeur si .svID [] .ID = "" ( sv obtiendra la valeur slotID

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

Si vous avez téléchargé l'objet à l'aide de curl, voici mon approche pour renommer certaines variables en un nom convivial comme extraire des données à partir de tableaux de données

utiliser eval et des filtres résoudra le problème avec une seule ligne et produira des variables avec le nom souhaité

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

L'avantage dans ce cas, c'est le fait qu'il va filtrer, renommer, formater toutes les variables souhaitées dans la première étape. Observez cela dedans. [0] | il est très courant d'avoir si la source provient d'un serveur API RESTFULL utilisant GET, les données de réponse comme:

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

Si vos données ne proviennent pas d'un tableau, par exemple. est un objet comme:

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

il suffit de supprimer l'index initial:

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

C'est une vieille question, mais je me sentais partager, car il était difficile de trouver

Thiago Conrado
la source