I have a sample json like this /tmp/test.json
{
"foo" : "val1",
"bar" : "val2"
}
Setting the key in a variable
key=foo
cat /tmp/test.json | jq --arg k $key '.[$k]'
# Output is fine
"val1"
Now if I want to store the whole command into a variable CMD
so that I can echo "$CMD
and eval "$CMD"
later:
CMD=`echo cat /tmp/test.json | jq --arg k $key '.[$k]'`
# Error outout
jq: parse error: Invalid numeric literal at line 1, column 4
How do I escape these special chars?
2
Don’t store the command in a variable; define a function.
CMD () {
jq --arg k $key '.[$k]' /tmp/test.json
}
and call the function later:
$ key=foo
$ CMD
"val1"
Rather than having CMD
rely on a global variable key
to be defined, consider parameterizing the function.
CMD () {
jq --arg k "$1" '.[$k]' /tmp/test.json
}
$ CMD foo
"val1"
$ CMD bar
"val2"
3
If you want to print the exact command before execution, and for some reason set -x
isn’t suitable, you can do this with an array (without creating security vulnerabilities as with eval
):
## Define your command as an array
myCmd=( jq --arg k "$k" '.[$k]' /tmp/test.json )
## Create a string that, if it were eval'd, would be equivalent to that command
# bash 3.2+ version
printf -v myCmd_q '%q ' "${myCmd[@]}" # use this OR the below
# bash 5.0+ version
myCmd_q=${myCmd[*]@Q} # use this OR the above
## Write that string to the user
printf 'About to run: %sn' "$myCmd_q" >&2
## Actually run the command
"${myCmd[@]}" # identical behavior to ''eval "$myCmd_q"''
Note that after using either printf '%q ' "${array[@]}"
or "${array[*]@Q}"
to generate an eval-safe string, you do have a string that’s eval
-safe — you could use eval
without otherwise-inherent security problems at that point; but since you also still have the array that the string was built from, it’s easier (and less likely to raise security-scanner flags) to just execute that array directly.
The use of printf
instead of echo
is for robustness, reliability, and portability reasons, as explained in detail in Why is printf better than echo? over on our sister site UNIX & Linux.
Here’s a simpler way to achieve what you’re trying to do:
- Read test.json with jq instead of passing from cat
- Use builtin env instead of
--arg k $key
.[foo]
is wrong – use.foo
or.["foo"]
instead
$ CMD="$(key=foo jq -r '"jq ".(env.key)" (input_filename)"' test.json)"
$ echo $CMD
jq ".foo" test.json
$ eval "$CMD"
"val1"
Same as above but with notes
# how to use jq's env:
# 1) export key (export key=foo)
# 2) prepend command with variable (key=foo jq '...')
export key=foo
# output: jq ".foo" test.json'
jq -r '"jq ".(env.key)" (input_filename)"' test.json
# sets CMD to 'jq ".foo" test.json'
CMD="$(jq -r '"jq ".(env.key)" (input_filename)"' test.json)"
# output: "val1"
eval "$CMD"
3