8

Say, I provide jq with the following JSON body as input:

{"person1": {"name": "foo"}, "person2": {"name": "bar"}}

Is it possible to have jq assign person1.name to environment variable PERSON_1_NAME and person2.name to environment variable PERSON_2_NAME, so that I don't have to run jq multiple times with the same input, e.g.:

INPUT='{"person1": {"name": "foo"}, "person2": {"name": "bar"}}'
export PERSON_1_NAME=$(echo $INPUT | jq -r .person1)

?

3 Answers 3

8

You can do this like so:

INPUT='{"person1": {"name": "foo"}, "person2": {"name": "bar"}}'
eval "$(echo "$INPUT" | jq -r '[
  "export PERSON_1_NAME="+.person1.name,
  "export PERSON_2_NAME="+.person2.name
] | .[]')"

Output:

$ INPUT='{"person1": {"name": "foo"}, "person2": {"name": "bar"}}'
eval "$(echo "$INPUT" | jq -r '[
  "export PERSON_1_NAME="+.person1.name,
  "export PERSON_2_NAME="+.person2.name
] | .[]')"
$ echo $PERSON_1_NAME
foo
$ echo $PERSON_2_NAME
bar

If you have security concerns about the type of data you'll be handling, you can also avoid using eval to prevent any issues:

mapfile -t names < <(echo "$INPUT" | jq -r '.person1.name, .person2.name')
export PERSON_1_NAME="${names[0]}"
export PERSON_2_NAME="${names[1]}"

Output:

$ INPUT='{"person1": {"name": "foo"}, "person2": {"name": "bar"}}'
$ mapfile -t names < <(echo "$INPUT" | jq -r '.person1.name, .person2.name')
export PERSON_1_NAME="${names[0]}"
export PERSON_2_NAME="${names[1]}"
$ echo $PERSON_1_NAME
foo
$ echo $PERSON_2_NAME
bar
2
  • 5
    Depending on shell settings, echo may decide to replace things like \t and \n with literal tabs and newline characters, which would break the JSON. Pass the JSON using --argjson instead. To handle quotations in the JSON values, consider using the @sh output operator, or you may face issues when having to handle names containing apostrophes. Commented Feb 19 at 4:45
  • The creation of an array in your first command seems unnecessary. Commented Feb 19 at 4:46
8

Assuming you want to use the key as part of the variable name, and that the key always has a sane value that can be used as part of a shell variable's name (we will not be verifying this), and that the name key holds a scalar that can be used as the value in a shell variable:

$ input='{"person1": {"name": "foo"}, "person2": {"name": "bar"}}'
$ eval "$(jq -n -r --argjson data "$input" '$data | to_entries[] | @sh "export \(.key)_name=\(.value.name)"')"
$ printf '%s\n' "$person1_name" "$person2_name"
foo
bar

This uses jq to create output like

export 'person1'_name='foo'
export 'person2'_name='bar'

... given the keys and the name sub-keys document passed as the jq variable $data.

The @sh output operator does some basic handling of shell escapes, which means that this also handles the cases where a name contains tabs, newlines, and quotes:

$ input='{"person1": {"name": "foo\tbar\nthe third"}, "person2": {"name": "A'\''Tuin"}}'
$ eval "$(jq -n -r --argjson data "$input" '$data | to_entries[] | @sh "export \(.key)_name=\(.value.name)"')"
$ printf '%s\n' "$person1_name" "$person2_name"
foo     bar
the third
A'Tuin

A jq command that creates the variable names in the format shown in the question, with the number counting up with each person:

$ jq -n -r --argjson data "$input" 'reduce ($data|to_entries[]) as $e ([]; . += [@sh "export PERSON_\(1+length)_NAME=\($e.value.name)"]) | .[]'
export PERSON_1_NAME='foo'
export PERSON_2_NAME='bar'
1
  • Kudos for giving a data driven answer. Having the result change for new fields in the input seems like a good idea for most use cases. Commented Feb 25 at 18:21
0

Some care is required with the quoting here:

eval "$(jq -r '@sh "export PERSON_1_NAME=\(.person1.name) PERSON_2_NAME=\(.person2.name)"' <<< '{"person1": {"name": "foo"}, "person2": {"name": "bar"}}')"

jq's @sh format/escape string does its best to escape output properly for shell ingestion.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.