Your original version isn't going to be eval
able because the author name has spaces in it - it would be interpreted as running a command Doe
with the environment variable AUTHOR
set to John
. There's also virtually never a need to pipe jq
to itself - the internal piping & dataflow can connect different filters together.
All of this is only sensible if you completely trust the input data (e.g. it's generated by a tool you control). There are several possible problems otherwise detailed below, but let's assume the data itself is certain to be in the format you expect for the moment.
You can make a much simpler version of your jq program:
jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)'
which outputs:
URL='example.com' AUTHOR='John Doe' CREATED='10/22/2017'
There's no need for a map
: .[]
deals with taking each object in the array through the rest of the pipeline as a separate item, so everything after the last |
is applied to each one separately. At the end, we just assemble a valid shell assignment string with ordinary +
concatenation, including appropriate quotes & escaping around the value with @sh
.
All the pipes matter here - without them you get fairly unhelpful error messages, where parts of the program are evaluated in subtly different contexts.
This string is eval
able if you completely trust the input data and has the effect you want:
eval "$(jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)' < data.json)" echo "$AUTHOR"
As ever when using eval
, be careful that you trust the data you're getting, since if it's malicious or just in an unexpected format things could go very wrong. In particular, if the key contains shell metacharacters like $
or whitespace, this could create a running command. It could also overwrite, for example, the PATH
environment variable unexpectedly.
If you don't trust the data, either don't do this at all or filter the object to contain just the keys you want first:
jq '.SITE_DATA | { AUTHOR, URL, CREATED } | ...'
You could also have a problem in the case that the value is an array, so .value | tostring | @sh
will be better - but this list of caveats may be a good reason not to do any of this in the first place.
It's also possible to build up an associative array instead where both keys and values are quoted:
eval "declare -A data=($(jq -r '.SITE_DATA | to_entries | .[] | @sh "[\(.key)]=\(.value)"' < test.json))"
After this, ${data[CREATED]}
contains the creation date, and so on, regardless of what the content of the keys or values are. This is the safest option, but doesn't result in top-level variables that could be exported. It may still produce a Bash syntax error when a value is an array, or a jq error if it is an object, but won't execute code or overwrite anything.