Modify a key-value in a json using jq in-place

I have a json in which I want to modify a particular value but the terminal always displays the json with the modified value but it does not actually change the value in the particular file. Sample json:

{
name: 'abcd',
age: 30,
address: 'abc'
}

I want to change the value of address in the file itself but so far I've been unable to do so. I tried using:

jq '.address = "abcde"' test.json

but it didn't work. Any suggestions?

118984 次浏览

AFAIK jq does not support in-place editing, so you must redirect to a temporary file first and then replace your original file with it, or use sponge utility from the moreutils package, like that:

jq '.address = "abcde"' test.json|sponge test.json

There are other techniques to "redirect to the same file", like saving your output in a variable e.t.c. "Unix & Linux StackExchange" is a good place to start, if you want to learn more about this.

Use a temporary file; it's what any program that claims to do in-place editing is doing.

tmp=$(mktemp)
jq '.address = "abcde"' test.json > "$tmp" && mv "$tmp" test.json

If the address isn't hard-coded, pass the correct address via a jq argument:

address=abcde
jq --arg a "$address" '.address = $a' test.json > "$tmp" && mv "$tmp" test.json

Just to add to chepner answer and if you want it in a shell script.

test.json

{
"name": "abcd",
"age": 30,
"address": "abc"
}

script.sh

#!/bin/bash
address="abcde"
age=40


# Strings:
jq --arg a "${address}" '.address = $a' test.json > "tmp" && mv "tmp" test.json


# Integers:
jq --argjson a "${age}" '.age = $a' test.json > "tmp" && mv "tmp" test.json

Temp files add more complexity when not needed (unless you are truly dealing with JSON files so large you cannot fit them in memory (GB to 100's of GB or TB, depending on how much RAM/parallelism you have)

The Pure bash way.

contents="$(jq '.address = "abcde"' test.json)" && \
echo -E "${contents}" > test.json

Pros

  • No temp file to juggle
  • Pure bash
  • Don't need an admin to install sponge, which is not installed by default
  • Simpler

Cons

  • This works perfectly fine for json because it cannot contain a literal null character. If you were to try this outside the json arena, it would fail when a null is encountered (and you would have to do some encoding/decoding workarounds). Bash variables cannot store literal nulls.

Note: this can not be combined as "one command" (like @codekandis suggested), since redirection sometimes starts before the left hand side (LHS) of an expression is run, and starting redirection before running jq erroneously empties the file, hence two separate commands. It may "seem" to work when you try it, but this is misleading and has a very high probability of failing as soon as the circumstances change.

Update: Added -E option to disable escape characters just in case you are on systems where they are interpreted by default. (Which I've never actually seen)

Example for nested json with changing single and multiple values.

config.json

{
"Parameters": {
"Environment": "Prod",
"InstanceType": "t2.micro",
"AMIID": "ami-02d8e11",
"ConfigRegion": "eu-west-1"
}
}

with the below command, you can edit multiple values.

tmp=$(mktemp)
jq '.Parameters.AMIID = "ami-02d8sdfsdf" | .Parameters.Environment = "QA"' config.json > "$tmp" && mv "$tmp" config.json

with the below command, you can edit single value.

tmp=$(mktemp)
jq '.Parameters.AMIID = "ami-02d8sdfsdf"' config.json > "$tmp" && mv "$tmp" config.json

I didn't like any of the solutions and created the sde utility.

pip install sde

Then, e.g. for the following JSON data:

{
"Parameters": {
"Environment": "Prod",
"InstanceType": "t2.micro",
"AMIID": "ami-02d8e11",
"ConfigRegion": "eu-west-1"
}
}

you can simply do:

sde Parameters.Environment Dev test.json

this should work

address = aaaaa
echo $(jq --arg a "$address" '.address = ($a)' test.json) > test.json

for whatever reason, without the echo, it makes a bin file and my python script was not able to parse it.

I took the best of a couple answers here and here.

This uses a parameter named actionname as an input to an assignment of the name property at the document level. ACTION_NAME is just an envvar I want to use as the replacement value.

contents="$(jq --arg actionname ${ACTION_NAME} '.name = $actionname' ./${ACTION_NAME}/package.json)" && \
echo -E "${contents}" > ${ACTION_NAME}/package.json;