115

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?

Cornelius Roemer
  • 1,853
  • 13
  • 32
wolfsbane
  • 1,379
  • 2
  • 9
  • 17
  • Does this answer your question? [Jq to replace text directly on file (like sed -i)](https://stackoverflow.com/questions/36565295/jq-to-replace-text-directly-on-file-like-sed-i) – Andy Apr 05 '20 at 20:27

7 Answers7

127

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
chepner
  • 446,329
  • 63
  • 468
  • 610
  • 1
    this worked for a hardcoded string. Is there any solution for a variable i.e $address – ahmed_khan_89 Oct 29 '19 at 15:31
  • 1
    @ahmed_khan_89 you can use jq '.address = "'${address}'"' – Pujan Dec 04 '19 at 14:25
  • 12
    No, do not interpolate strings into a `jq` filter. Use `jq --arg a "$address" '.address = $a'` instead. – chepner Dec 04 '19 at 15:36
  • 1
    @chepner How come you don't recommend interpolating the string? It works when I use Pujan's method – Alexander D Jun 09 '20 at 21:49
  • It's an enormous headache to deal with the quoting. Use --arg – hagemt Oct 13 '20 at 20:40
  • 1
    @AlexanderD Because interpolation doesn't necessarily pass an argument to your filter; it *builds* a filter, and that filter depends on how the variable expansion is parsed. – chepner Oct 13 '20 at 21:06
  • Notice the file owner and file permission of the `test.json` will be altered due to use of `mv`. I usually use `cat` instead. – VCD Dec 04 '20 at 03:15
  • `mv` is atomic; `cat` is not. You can adjust the permissions and owner of the temp file before executing `mv`, but preventing another process from accessing `test.json` while `cat` overwrites its contents is more difficult. – chepner Dec 04 '20 at 12:37
103

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.

zeppelin
  • 8,641
  • 2
  • 22
  • 30
  • Unfortunately moreutils / sponge is unavailable on CentOS 8 atm.. – willemdh Oct 19 '19 at 12:40
  • 20
    Without `sponge`: `echo "$( jq '.address = "abcde"' test.json )" > test.json` – codekandis Aug 03 '20 at 09:35
  • 1
    Be careful! Pass the filename as an argument to `sponge`, as in the answer. This is wrong: `jq . test.json | sponge > test.json` , you must do `jq . test.json | sponge test.json` – Flimm Sep 25 '20 at 17:14
  • 1
    An additional nod for getting me to look at sponge(1) – sberder Feb 17 '21 at 04:24
  • 1
    do not redirect to the same file as suggested by @codekandis comment. this will not always work. Large files it will cause issues, also with whitespaces, non-printable and escapment sequences. Never redirect a file to itself, it is always a bad idea. See sponge or use a temp file, just don't try to do it differently unless you understand what is happening. – Lynch Jul 18 '21 at 19:22
48

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)

Andy
  • 2,604
  • 1
  • 14
  • 23
  • 4
    This answer is particularly useful for things such as bumping the version on your package.json (e.g. `contents="$(jq '.version = "$version"' package.json)" && echo "${contents}" > package.json`) – Caleb Faruki Dec 11 '20 at 11:11
9

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
Rafal Kita
  • 121
  • 1
  • 3
5

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
mahendra rathod
  • 1,105
  • 1
  • 10
  • 18
3

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.

Delcroip
  • 51
  • 2
  • Streaming the result into the input file is generally risky in shell scripts: `>` normally causes the output file to be truncated before the rest of the command is executed. Possibly the subshell command `$(...)` postpones this until after the execution of the shell script. In general I would always use a command line option to deal with this special case, or lacking that, write to an intermediate file. – user1010997 Nov 17 '21 at 09:10
-1

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

pip install sde

Then you can simply do:

sde address abcde test.json
Danila Vershinin
  • 7,428
  • 2
  • 24
  • 33