106

I need your help to solve the following problem: I have a JSON file that looks like this:

{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}

how can I add and remove a new key (i.e "key4": "value4") by bash script? I see also the issue to add or remove a comma at the end of last key in the file before adding or removing the new one.

Thank you

1
  • 1
    Does it have to be bash or can you use Node.js? This seems like a great problem to use node for. Commented Jul 24, 2014 at 20:34

6 Answers 6

170

Your best bet is to use a JSON CLI such as jq:

  • On Debian-based systems such as Ubuntu, you can install it via
    sudo apt-get install jq
  • On macOS, with Homebrew (http://brew.sh/) installed, use
    brew install jq

Examples, based on the following input string - output is to stdout:

jsonStr='{ "key1": "value1", "key2": "value2", "key3": "value3" }'
Remove "key3":
jq 'del(.key3)' <<<"$jsonStr"
Add property "key4" with value "value4":
jq '. + { "key4": "value4" }' <<<"$jsonStr"
Change the value of existing property "key1" to "new-value1":
jq '.key1 = "new-value1"' <<<"$jsonStr"

A more robust alternative thanks, Lars Kiesow :
If you pass the new value with --arg, jq takes care of properly escaping the value:

jq '.key1 = $newVal' --arg newVal '3 " of rain' <<<"$jsonStr"

If you want to update a JSON file in place (conceptually speaking), using the example of deleting "key3":

# Create test file.
echo '{ "key1": "value1", "key2": "value2", "key3": "value3" }' > test.json

# Remove "key3" and write results back to test.json (recreate it with result).
jq -c 'del(.key3)' test.json > tmp.$$.json && mv tmp.$$.json test.json

You cannot replace the input file directly, so the result is written to a temporary file that replaces the input file on success.

Note the -c option, which produces compact rather than pretty-printed JSON.

For all options and commands, see the manual at https://jqlang.github.io/jq/manual/.

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

4 Comments

I am recevied a output from a script which is actually in purple color, when I replace it withing the file it set as in between following \u001b[0;1;34m \u001b[0;m, does jq able to ignore coloring of a string?
Thank you @mklement0 I was able solve it by using following answer (stackoverflow.com/a/14693789/2402577) in order to remove the ANSI character.
You can use sponge (from moreutils package) to edit the file directly jq -c 'del(.key3)' test.json | sponge.exe test.json It's also destructive, so be careful
I couldn't easily use sponge from the VS Cod shell, so your solution to update a JSON file in place was quit useful
56

Not the answer for everyone, but if you already happen to have NodeJs installed in your system, you can use it to easily manipulate JSON.

eg:

#!/usr/bin/env bash
jsonFile=$1;

node > out_${jsonFile} <<EOF
//Read data
var data = require('./${jsonFile}');

//Manipulate data
delete data.key3
data.key4 = 'new value!';

//Output data
console.log(JSON.stringify(data));

EOF

Heck, if you only need to do JSON manipulation and you have node (ie: You don't really need any other bash functionality) you could directly write a script using node as the interpreter:

#! /usr/bin/env node
var data = require('./'+ process.argv[2]);
/*manipulate*/
console.log(JSON.stringify(data));

5 Comments

Is the console.log() supposed to write the data to the file or do I need fs?
prints to stdout. You can then redirect to a file. eg: ./myScript.js myInput.json > outfile.txt
any programming language with a JSON library could do the exact same. The main point of using bash is that you don't rely on software that isn't already installed on your system. If that is not a requirement, then there's no reason to suggest Node for this over any other language, and just because you can run it inline from a bash script doesnt make it a bash answer.
I started my answer with "Not the answer for everyone," for that precise reason. There are many ways to skin a cat, and in the three years since I posted this answer, Node has continued to grow in popularity, so many people have it installed already. It offers native JSON support, so no need for a library is nice. At the end of the day, I shared a bit of knowledge, and it has helped 16 people. That makes me happy :)
Excellent idea for teams where all devs may not have the same tools installed.
21

Building off Lenny's answer, we can use node's -p option, which evaluates the given script and writes the output to stdout.

Using the spread operator for easy modification gives:

node -p "JSON.stringify({...require('./data.json'), key4: 'value4'}, null, 2)" > data.json

1 Comment

This follow-up to Lenny's answer did the trick in my case. I like that it is just a one-liner with no need to care for the shebang at the top of my script. However, for more complex manipulations it may be more appropriate to stay closer to Lenny's multi-line version.
8

to change a file in place, use the sponge command

echo '{ "k": "old value" }' >f.json

cat f.json | jq '.k = $v' --arg v 'new value' | sponge f.json

see also: jq issue Edit files in place #105

alternative to jq: jaq

echo '{ "k": "old value" }' >f.json

jaq -i '.k = $v' --arg v 'new value' f.json

... but jaq has less features than jq

2 Comments

For those who, like me, were confused by what sponge is: joeyh.name/code/moreutils
to install sponge you need : sudo apt install moreutils
4

Here's a pure bash example, including the "comma issue".

#!/bin/bash
# This bash script just uses the sed command to 
#   replace/insert a new key at/before/after an 
#   existing key in a json file 
# The comma issue:
# - replace: with/without, as previous entry
# - before: always add
# - after: add before, if there was none
SED_CMD="/tmp/sed_cmd.tmp"
JSFILE1="./data1.json"
JSFILE2="./data2.json"
JSFILE3="./data3.json"
SEARCH_KEY="key3"
# create json input file
echo -e '{\n\t"key1": "value1",\n\t"key2": "value2",\n\t"key3": "value3"\n}' > $JSFILE1
echo -e "input:"
cat $JSFILE1
# duplicate twice
cp $JSFILE1 $JSFILE2 && cp $JSFILE1 $JSFILE3
# find the SEARCH_KEY and store the complete line to SEARCH_LINE 
SEARCH_LINE=`cat data.json | grep $SEARCH_KEY`
echo "SEARCH_LINE=>$SEARCH_LINE<"
# replace SEARCH_LINE
IS_COMMA=`echo $SEARCH_LINE | grep ","`
[ -z "$IS_COMMA" ] && \
    echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\"+g" > $SED_CMD || \
    echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\",+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE1
echo -e "replace:"
cat $JSFILE1
# insert before SEARCH_LINE
echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\",\n$SEARCH_LINE+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE2
echo -e "before:"
cat $JSFILE2
# insert after SEARCH_LINE
IS_COMMA=`echo $SEARCH_LINE | grep ","`
[ -z "$IS_COMMA" ] && \
    echo "s+$SEARCH_LINE+$SEARCH_LINE,\n\t\"keyNew\": \"New\"+g" > $SED_CMD || \
    echo "s+$SEARCH_LINE+$SEARCH_LINE\n\t\"keyNew\": \"New\",+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE3
echo -e "after:"
cat $JSFILE3
exit 0

1 Comment

Seems to work pretty well. Just as a hint this example script expects a data.json and generates data1.json data2.json and data3.json (one for each use case).
-1

how can I add and remove a new key (i.e "key4": "value4") by bash script?

Please use a dedicated JSON parser, like .

Add a new attribute:

$ xidel -s "input.json" -e '($json).key4:="value4"'          # Xidel exclusive syntax, dot notation)
$ xidel -s "input.json" -e '$json("key4"):="value4"'         # Xidel exclusive syntax, JSONiq notation)
$ xidel -s "input.json" -e '$json?key4:="value4"'            # Xidel exclusive syntax, XPath 3.1 syntax)
$ xidel -s "input.json" -e '{|$json,{"key4":"value4"}|}'     # JSONiq syntax (deprecated)
$ xidel -s "input.json" -e 'map:put($json,"key4",4)'         # XQuery 3.1 map functions
$ xidel -s "input.json" -e 'map:merge(($json,{"key4":4}))'   # XQuery 3.1 map functions
{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3",
  "key4": "value4"
}

Remove the attribute "key3":

$ xidel -s "input.json" --xmlns:jnlib="http://jsoniq.org/function-library" -e 'jnlib:remove-keys($json,"key3")'   # JSONiq jnlib functions (deprecated)
$ xidel -s "input.json" -e 'map:remove($json,"key3")'   # XQuery 3.1 map functions
{
  "key1": "value1",
  "key2": "value2"
}

Change the attribute "key3" value to "value4":

$ xidel -s "input.json" -e '($json).key3:="value4"'           # Xidel exclusive syntax, dot notation)
$ xidel -s "input.json" -e '$json("key3"):="value4"'          # Xidel exclusive syntax, JSONiq notation)
$ xidel -s "input.json" -e '$json?key3:="value4"'             # Xidel exclusive syntax, XPath 3.1 syntax)
$ xidel -s "input.json" -e 'map:put($json,"key3","value4")'   # XQuery 3.1 map functions
$ xidel -s "input.json" -e 'map:merge(($json,{"key3":"value4"}),{"duplicates":"use-last"})'   # XQuery 3.1 map functions
{
  "key1": "value1",
  "key2": "value2",
  "key3": "value4"
}

Comments

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.