Array JSON ke variabel bash menggunakan jq

19

Saya punya array JSON seperti ini:

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

Saya mencari untuk mengulangi array ini menggunakan jq sehingga saya dapat mengatur kunci setiap item sebagai nama variabel dan nilai sebagai nilainya.

Contoh:

  • URL = "example.com"
  • PENULIS = "John Doe"
  • DICIPTAKAN = "10/22/2017"

Apa yang saya dapatkan sejauh ini mengulangi array tetapi membuat string:

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

Output yang mana:

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

Saya ingin menggunakan variabel-variabel ini lebih jauh ke bawah dalam skrip:

echo ${URL}

Tapi ini menggemakan output kosong saat ini. Kurasa aku butuh evalsesuatu atau sesuatu di sana, tetapi sepertinya tidak bisa meletakkan jari di atasnya.

EHerman
sumber

Jawaban:

29

Versi asli Anda tidak akan evalbisa karena nama penulis memiliki spasi di dalamnya - itu akan ditafsirkan sebagai menjalankan perintah Doedengan variabel lingkungan AUTHORdiatur ke John. Ada juga hampir tidak pernah perlu untuk pipa jqke dirinya sendiri - perpipaan & aliran data internal dapat menghubungkan filter yang berbeda bersama-sama.

Anda dapat membuat versi yang lebih sederhana dari program jq:

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

yang keluaran:

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

Tidak perlu untuk map: .[]berurusan dengan mengambil setiap objek dalam array melalui sisa pipa sebagai item terpisah , jadi semuanya setelah yang terakhir |diterapkan untuk masing-masing secara terpisah. Pada akhirnya, kami hanya merakit string penugasan shell yang valid dengan +penggabungan biasa , termasuk kutipan di sekitar nilai.

Semua masalah pipa di sini - tanpa mereka Anda mendapatkan pesan kesalahan yang cukup tidak membantu, di mana bagian dari program dievaluasi dalam konteks yang agak berbeda.

String ini adalah evalmampu selama karakter `, $, baris baru dan nol tidak muncul dalam data:

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

Seperti biasa saat menggunakan eval, berhati-hatilah agar Anda memercayai data yang Anda peroleh, karena jika itu berbahaya atau hanya dalam format yang tidak terduga, semuanya bisa salah.

Michael Homer
sumber
13

Berdasarkan jawaban @Michael Homer, Anda dapat menghindari evalsepenuhnya berpotensi tidak aman dengan membaca data ke dalam array asosiatif.

Misalnya, jika data JSON Anda dalam file bernama 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]}'"

Keluaran:

$ ./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
sumber
1
Anda juga bisa secara tidak langsung menugaskan tugas dengan declare -- “$key=$value”dan memiliki $AUTHORpekerjaan dll seperti pada aslinya, tanpa array. Ini masih lebih aman daripada eval, meskipun perubahan PATHatau sesuatu masih mungkin jadi kurang dari versi ini.
Michael Homer
1
yeah, array dengan baik mengisolasi variabel ke dalam wadah pilihan Anda - tidak ada kesempatan untuk secara tidak sengaja / jahat mengacaukan variabel lingkungan penting. Anda dapat membuat declare --versi Anda aman dengan membandingkan $ key dengan daftar nama variabel yang diizinkan.
cas
1

Baru menyadari bahwa saya dapat mengulangi hasil dan mengevaluasi setiap iterasi:

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

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

Mengizinkan saya melakukan:

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

Saya sangat suka saran @Michel. Terkadang, Anda mungkin benar-benar mengekstrak beberapa nilai variabel untuk menjalankan tugas di server tertentu menggunakan BASH. Jadi, variabel yang diinginkan adalah tahu. Ini menggunakan pendekatan ini adalah cara untuk menghindari atau beberapa panggilan ke jq untuk menetapkan nilai per variabel atau bahkan untuk menggunakan pernyataan baca dengan beberapa variabel di mana beberapa dapat valid dan kosong, yang mengarah ke pergeseran nilai (yang merupakan masalah saya)

Pendekatan saya sebelumnya yang mengarah akan menyebabkan kesalahan nilai pergeseran jika .svID [] .ID = "" ( sv akan mendapatkan slotID nilai

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

Jika Anda mengunduh objek menggunakan curl, berikut adalah pendekatan saya untuk mengubah nama beberapa variabel menjadi nama yang ramah sebagai mengekstrak data dari array data

menggunakan eval dan filter akan menyelesaikan masalah dengan satu baris dan akan menghasilkan variabel dengan nama yang diinginkan

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

Keuntungan dalam hal ini, adalah kenyataan bahwa ia akan memfilter, mengganti nama, memformat semua variabel yang diinginkan pada langkah pertama. Perhatikan bahwa ada. [0] | yang sangat umum untuk memiliki jika sumber jika dari server API RESTFULL menggunakan GET, respons data sebagai:

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

Jika data Anda bukan dari array, mis. adalah objek seperti:

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

hapus saja indeks awal:

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

Ini adalah pertanyaan lama, tetapi saya merasa berbagi, karena sulit ditemukan

Thiago Conrado
sumber