Matriz JSON para bash variáveis ​​usando jq

18

Eu tenho uma matriz JSON assim:

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

Eu estou olhando para iterar essa matriz usando jq para que eu possa definir a chave de cada item como o nome da variável e o valor como ele é.

Exemplo:

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

O que eu tenho até agora itera sobre a matriz, mas cria uma string:

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

Quais saídas:

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

Eu estou olhando para usar essas variáveis ​​mais abaixo no script:

echo ${URL}

Mas isso ecoa uma saída vazia no momento. Estou supondo que preciso de evalalgo ou algo lá, mas não consigo apontar o dedo.

EHerman
fonte

Respostas:

28

Sua versão original não será evalcapaz porque o nome do autor possui espaços - ela seria interpretada como executando um comando Doecom a variável de ambiente AUTHORdefinida como John. Também praticamente nunca há necessidade de canalizar jqpara si próprio - a tubulação interna e o fluxo de dados podem conectar diferentes filtros.

Você pode criar uma versão muito mais simples do programa jq:

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

quais saídas:

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

Não há necessidade de map: .[]trata de levar cada objeto da matriz pelo restante do pipeline como um item separado ; portanto, tudo depois do último |é aplicado a cada um separadamente. No final, apenas montamos uma string de atribuição de shell válida com +concatenação comum , incluindo aspas em torno do valor.

Todos os canais são importantes aqui - sem eles, você recebe mensagens de erro inúteis, nas quais partes do programa são avaliadas em contextos sutilmente diferentes.

Essa seqüência é evalcapaz, enquanto os personagens `, $, nova linha e nulo não aparecem nos dados:

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

Como sempre, ao usar eval, tenha cuidado para confiar nos dados que está obtendo, pois, se forem maliciosos ou apenas em um formato inesperado, as coisas podem dar muito errado.

Michael Homer
fonte
13

Com base na resposta de @Michael Homer, você pode evitar um potencial potencialmente inseguro evallendo os dados em uma matriz associativa.

Por exemplo, se seus dados JSON estiverem em um arquivo chamado 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]}'"

Resultado:

$ ./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
fonte
1
Você também pode indiretamente declare -- “$key=$value”atribuir a tarefa com, e $AUTHORetc, trabalhar como no original, sem uma matriz. Ainda é mais seguro do que avaliar, embora a alteração PATHou algo ainda seja possível, menos ainda do que esta versão.
Michael Homer
1
sim, a matriz isola bem as variáveis ​​em um contêiner de sua escolha - sem chance de mexer acidental / maliciosamente com variáveis ​​importantes do ambiente. você pode tornar sua declare --versão segura comparando $ key com uma lista de nomes de variáveis ​​permitidos.
cas
1

Acabei de perceber que posso percorrer os resultados e avaliar cada iteração:

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

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

Permite que eu faça:

echo ${AUTHOR}
# outputs John Doe
EHerman
fonte
0

Eu realmente gosto da sugestão @Michel. Às vezes, você pode realmente apenas extrair o valor de algumas variáveis ​​para executar uma tarefa nesse servidor específico usando o BASH. Portanto, as variáveis ​​desejadas são conhecidas. O uso dessa abordagem é a maneira de evitar ou várias chamadas para jq para definir um valor por variável ou até mesmo usar a instrução read com várias variáveis ​​nas quais algumas podem ser válidas e vazias, levando a uma mudança de valor (esse era o meu problema).

minha abordagem anterior de que o lead levará a um erro de mudança de valor se .svID [] .ID = "" ( sv obterá o valor do slotID

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

Se você baixou o objeto usando curl, aqui está minha abordagem para renomear algumas variáveis ​​para um nome amigável, como extrair dados de matrizes de dados

usar eval e filtros resolverá o problema com uma linha e produzirá variáveis ​​com o nome desejado

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

A vantagem, neste caso, é o fato de filtrar, renomear e formatar todas as variáveis ​​desejadas na primeira etapa. Observe que existe. [0] | é muito comum ter, se a origem for de um servidor RESTFULL API usando GET, dados de resposta como:

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

Se seus dados não são de uma matriz, ou seja. é um objeto como:

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

basta remover o índice inicial:

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

Esta é uma pergunta antiga, mas senti compartilhar, pois era difícil encontrar

Thiago Conrado
fonte