2

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.

Cyrus
  • 84,225
  • 14
  • 89
  • 153
mark
  • 59,016
  • 79
  • 296
  • 580

3 Answers3

2

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?

Cyrus
  • 84,225
  • 14
  • 89
  • 153
1

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
pmf
  • 24,478
  • 2
  • 22
  • 31
  • I was deliberating between your answer, the answer by ufopilot and the cyrus's answer. All are very educational and thus excellent, but cyrus was the first. But I learned from all. – mark Mar 19 '23 at 13:48
1
$ 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
ufopilot
  • 3,269
  • 2
  • 10
  • 12
  • 1
    I was deliberating between your answer, the answer by pmf and the cyrus's answer. All are very educational and thus excellent, but cyrus was the first and his answer is very similar to yours, even though a tiny little bit less concise. But I learned from all. – mark Mar 19 '23 at 13:47