jsonbashshellcurl

Parse JSON object's members into Bash variables


Let's assume, I have this package jq installed. So I ran this command

curl -s ipinfo.io/33.62.137.111

and get this result

{
  "ip": "33.62.137.111",
  "city": "Columbus",
  "region": "Ohio",
  "country": "US",
  "loc": "39.9690,-83.0114",
  "postal": "43218",
  "timezone": "America/New_York",
  "readme": "https://ipinfo.io/missingauth"
}

I know I can get city by doing this

curl -s ipinfo.io/33.62.137.111 | jq -r '.city' 

I know I can get region by doing this

curl -s ipinfo.io/33.62.137.111 | jq -r '. region' 

I'm trying to curl 7 times to create 7 variables.

Is there a way to create multiple variables based on the first curl response?


Solution

  • It is easy with Bash 4+ using an associative array:

    #!/usr/bin/env bash
    
    # Map the JSON response into an associative array
    declare -A "assoc_array=($(
      curl -s ipinfo.io/33.62.137.111 |
        jq -r 'to_entries[] | "[\(.key | @sh)]=\(.value | @sh)"'
    ))"
    IFS=, read -r assoc_array[lat] assoc_array[long] <<<"${assoc_array[loc]}"
    
    echo "Here is how assoc_array was declared/created"
    echo
    typeset -p assoc_array
    echo
    echo
    
    # Display the content of the associative array
    echo "Here is a breakdown of all entries in the assoc_array:"
    echo
    for k in "${!assoc_array[@]}"; do
      printf '%q = %q\n' "$k" "${assoc_array[$k]}"
    done
    

    Sample output:

    Here is how assoc_array was declared/created
    
    declare -A assoc_array=([country]="US" [region]="Ohio" [city]="Columbus" [timezone]="America/New_York" [ip]="33.62.137.111" [lat]="39.9690" [readme]="https://ipinfo.io/missingauth" [long]="-83.0114" [loc]="39.9690,-83.0114" [postal]="43218" )
    
    
    Here is a breakdown of all entries in the assoc_array:
    
    country = US
    region = Ohio
    city = Columbus
    timezone = America/New_York
    ip = 33.62.137.111
    lat = 39.9690
    readme = https://ipinfo.io/missingauth
    long = -83.0114
    loc = 39.9690\,-83.0114
    postal = 43218
    

    For older Bash, it is a bit trickier but here it is

    It separates values by the ASCII ETX (Value 3 for End of Text) and generates a stream of fields with jq, then read each variable into the predictable order. If a key is missing from the JSON response object, the field will be empty.

    Contrarily to the associative array method, key names have to be known beforehand and provided in a predicted order (all this is handled by the long jq query).

    #!/usr/bin/env bash
    
    IFS=$'\3' read -r ip hostname city region country lat long postal timezone readme < <(
      curl -s ipinfo.io/33.62.137.111 |
        jq -r '"\(.ip+"\u0003")\(.hostname+"\u0003")\(.city+"\u0003")\(.region+"\u0003")\(.country+"\u0003")\(.loc | split(",") |"\(.[0]+"\u0003")\(.[1]+"\u0003")")\(.postal+"\u0003")\(.timezone+"\u0003")\(.readme+"\u0003")"'
    )
    
    printf 'ip = %q\n' "$ip"
    printf 'hostname = %q\n' "$hostname"
    printf 'city = %q\n' "$city"
    printf 'region = %q\n' "$region"
    printf 'country = %q\n' "$country"
    printf 'latitude = %q\n' "$lat"
    printf 'longitude = %q\n' "$long"
    printf 'postal code = %q\n' "$postal"
    printf 'timezone = %q\n' "$timezone"
    printf 'readme = %q\n' "$readme"
    

    Sample output:

    ip = 33.62.137.111
    hostname = ''
    city = Columbus
    region = Ohio
    country = US
    latitude = 39.9690
    longitude = -83.0114
    postal code = 43218
    timezone = America/New_York
    readme = https://ipinfo.io/missingauth