4

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   } }
1
  • 4
    a little tip, don't login as root to do work. Commented Jan 6, 2017 at 10:25

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

Comments

5

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.

Comments

1

Using jq with a straight forward filter, should do it for you.

.global.name = "dsa"

i.e.

jq '.global.name = "dsa"' json-file
{
  "global": {
    "name": "dsa",
    "id": 1
  }
}

You can play around with your json-filters, here.

3 Comments

|= wasn't the problem; the quoting was.
@chepner: didn't catch it earlier.
Also note that filter is a very broad concept in jq (to quote from the manual: 'A jq program is a “filter”: it takes an input, and produces an output.'), whereas the feature of interest here is an assignment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.