skip to Main Content

Suppose we have the following JSON – {a:1,b:"x"}. I want to extract the properties a and b and assign them to the respective bash variables.

Here is how I do it today:

/c/Temp$ export $(jq -n '{a:1,b:"x"}' | sed -n 's,:,=,p' | tr -d ', "') && echo $a && echo $b
1
x

/c/Temp$

However, this only works when the values do not contain spaces, therefore my solution is incorrect.

I can also invoke jq one by one like this:

/c/Temp$ b=$(jq -n '{a:1,b:"x"}' | jq -r .b) && echo $b
x

/c/Temp$ a=$(jq -n '{a:1,b:"x"}' | jq -r .a) && echo $a
1

/c/Temp$

But I feel it is not the most concise solution.

What is the most concise solution?

For the purpose of this post, use this json:

{
  "a": 1,
  "b": "x y"
}

But the solution should work equally well for a JSON with 50 top level properties.

3

Answers


  1. If the content of your JSON is trustworthy:

    eval $(jq -r 'to_entries|map("export (.key)="(.value|tostring)"")|.[]' file.json)
    echo "$a"
    echo "$b"
    

    Output:

    1
    x y
    

    See: Why should eval be avoided in Bash, and what should I use instead?

    Login or Signup to reply.
  2. If the input is valid JSON (as with your second input – the first one lacked quoting the a key), and the keys are valid identifiers for shell variables (with no special characters like = or whitespace etc.), and the values don’t contain newlines (special characters like = or other whitespace are covered, though), then you could use read on a single jq filter as follows:

    For the two item version just name them directly:

    $ { read -r a; read -r b; } < <(jq -r '.a, .b' file.json)
    
    $ echo "$a"
    1
    
    $ echo "$b"
    x y
    

    For more items you could declare them in a while loop based on the output of a to_entries iteration (in the shell, use variable names (here $key and $value) that are not affected by the extraction):

    $ { while read -r key; read -r value; do declare "$key"="$value"; done; } < <(jq -r 'to_entries[][]' file.json)
    
    $ echo "$a"
    1
    
    $ echo "$b"
    x y
    

    For conciseness, you can have jq also generate the = between key and value, so only one variable (here $dec) is needed:

    $ { while read -r dec; do declare "$dec"; done; } < <(jq -r 'to_entries[] | join("=")' file.json)
    
    $ echo "$a"
    1
    
    $ echo "$b"
    x y
    
    Login or Signup to reply.
  3. $ source <(jq -r 'to_entries[]|"export (.key)="(.value)""' <<<'{"a":1,"b":"x y z"}')
    
    $ echo $a
    1
    $ echo $b
    x y z
    $ bash -c 'echo $b'
    x y z
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search