sed parameter substitution with multiline quoted string
Assuming your JSON document looks something like
{
"scripts": {
"other-key": "some value"
}
}
... and you'd like to insert some other key-value pair into the .scripts
object. Then you may use jq
to do this:
$ jq '.scripts.watch |= "tsc -w"' file.json
{
"scripts": {
"other-key": "some value",
"watch": "tsc -w"
}
}
or,
$ jq '.scripts += { watch: "tsc -w" }' file.json
{
"scripts": {
"other-key": "some value",
"watch": "tsc -w"
}
}
Both of these would replace an already existing .scripts.watch
entry.
Note that the order of the key-value pairs within .scripts
is not important (as it's not an array).
Redirect the output to a new file if you want to save it.
To add multiple key-value pairs to the same object:
$ jq '.scripts += { watch: "tsc -w", dev: "nodemon dist/index.js" }' file.json
{
"scripts": {
"other-key": "some value",
"watch": "tsc -w",
"dev": "nodemon dist/index.js"
}
}
In combination with jo
to create the JSON that needs to be added to the .scripts
object:
$ jq --argjson new "$( jo watch='tsc -w' dev='nodemon dist/index.js' )" '.scripts += $new' file.json
{
"scripts": {
"other-key": "some value",
"watch": "tsc -w",
"dev": "nodemon dist/index.js"
}
}
sed
is good for parsing line-oriented text. JSON does not come in newline-delimited records, and sed
does not know about the quoting and character encoding rules etc. of JSON. To properly parse and modify a structured data set like this (or XML, or YAML, or even CSV under some circumstances), you should use a proper parser.
As an added benefit of using jq
in this instance, you get a bit of code that is easily modified to suit your needs, and that is equally easy to modify to support a change in the input data structure.
Kusalananda is absolutely right saying that a dedicated parser is the right tool for the job. However, this can easily be done in sed
(at least with GNU sed
which understands \n
) as well. You were making things more complicated by trying to replace the entire two-line pattern. Instead, just match the target string and insert the replacement after it:
"scripts": {
And then:
$ sed '/"scripts": {/s/$/\n "watch": "tsc -w",/' file
"scripts": {
"watch": "tsc -w",
This means "if this line matches the string "scripts": {
, then replace the end of the line with a newline (\n
) followed by "watch": "tsc -w",
. Alternatively, you can use the a
sed command to append text:
$ sed '/"scripts": {/a\ "watch": "tsc -w",' file
"scripts": {
"watch": "tsc -w",
Here, we create a Sed command, but put a newline into it:
sed "s/$SRC/$DST/" foo.json
That's invalid, as the newline ends the command. We need to write \n
instead of a literal newline. In some shells (Bash, zsh, others..., but not plain POSIX shell), we can do that using a parameter substitution:
DST=${DST//$'\n'/\\n}
# // replace every
# $'\n' newline
# / with
# \\n \ and n
For other replacement strings, we may also need to quote /
, too:
DST=${DST//\//\\/}
Also, \
(do that one first) and &
have special meaning in replacement text and therefore would need replacement.