Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying JSON by using jq

Tags:

json

bash

jq

I want to modify a JSON file by using the Linux command line.

I tried these steps:

[root@localhost]# INPUT="dsa"
[root@localhost]# echo $INPUT
dsa
[root@localhost]# CONF_FILE=test.json
[root@localhost]# echo $CONF_FILE
test.json
[root@localhost]# cat $CONF_FILE
{
  "global" : {
    "name" : "asd",
    "id" : 1
  }
}
[root@localhost]# jq -r '.global.name |= '""$INPUT"" $CONF_FILE > tmp.$$.json && mv tmp.$$.json $CONF_FILE
jq: error: dsa/0 is not defined at <top-level>, line 1:
.global.name |= dsa
jq: 1 compile error

Desired output:

[root@localhost]# cat $CONF_FILE
    {   "global" : {
    "name" : "dsa",
    "id" : 1   } }
like image 367
high_hopes Avatar asked Dec 18 '22 10:12

high_hopes


2 Answers

Your only problem was that the script passed to jq was quoted incorrectly.

In your particular case, using a single double-quoted string with embedded \-escaped " instances is probably simplest:

jq -r ".global.name = \"$INPUT\"" "$CONF_FILE" > tmp.$$.json && mv tmp.$$.json "$CONF_FILE"

Generally, however, chepner's helpful answer shows a more robust alternative to embedding the shell variable reference directly in the script: Using the --arg option to pass a value as a jq variable allows single-quoting the script, which is preferable, because it avoids confusion over what elements are expanded by the shell up front and obviates the need for escaping $ instances that should be passed through to jq.

Also:

  • Just = is sufficient to assign the value; while |=, the so-called update operator, works too, it behaves the same as = in this instance, because the RHS is a literal, not an expression referencing the LHS - see the manual.
  • You should routinely double-quote your shell-variable references and you should avoid use of all-uppercase variable names in order to avoid conflicts with environment variables and special shell variables.

As for why your quoting didn't work:

'.global.name |= '""$INPUT"" is composed of the following tokens:

  • String literal .global.name |= (due to single-quoting)
  • String literal "" - i.e., the empty string - the quotes will be removed by the shell before jq sees the script
  • An unquoted reference to variable $INPUT (which makes its value subject to word-splitting and globbing).
  • Another instance of literal "".

With your sample value, jq ended up seeing the following string as its script:

.global.name |= dsa

As you can see, the double quotes are missing, causing jq to interpret dsa as a function name rather than a string literal, and since no argument was passed to (non-existent) function dsa, jq's error message referenced it as dsa/0 - a function with no (0) arguments.

like image 101
mklement0 Avatar answered Jan 12 '23 02:01

mklement0


It's much simpler and safer to pass the value using the --arg option:

jq -r --arg newname "$INPUT" '.global.name |= $newname' "$CONF_FILE"

This ensures that the exact value of $INPUT is used and quoted as a JSON value.

like image 32
chepner Avatar answered Jan 12 '23 02:01

chepner