Gamadril
Gamadril

Reputation: 927

How to replace a multiline block from a file (JSON format) with sed, awk or other OS X tools?

I'm looking for a one-liner to execute in the terminal to replace a multiline text block with my own context inside a text file. I'm on OSX (not GNU sed) and not able to install any additonal tools.

What I want to do is to replace in

{
    "user" :
    {
        "name": "Andreas",
        "age": 34
    },
    "viewer" :
    {
        "name": "Pedro",
        "age": 41
    }
}

two lines between the curly brackets inside the "user" block with own values to get the result:

{
    "user" :
    {
        "name": "Mike",
        "age": 29
    },
    "viewer" :
    {
        "name": "Pedro",
        "age": 41
    }
}

Simple search for the lines containing "name" or "age" would not work as they can belong to another structure and should not be modified.

By combining several examples I found I got this one working:

sed -i '' -n $'1h;1! H;$ {;g;s#"user"[^{]*[^}]*#"user" :\\\n\\\t{\\\n\\\t\\\t"name": "Mike",\\\n\\\t\\\t"age": 29\\\n\\\t#p;}' config.json

However it seems to be quite complex and here are my questions.

  1. How the matching pattern can be modified to detect only the content between the brackets, so I don't have to recreate the "user" key.
  2. Is there another more elegant solution? sed, awk or any other system tools inlcuded in OS X are welcome.

Upvotes: 2

Views: 1824

Answers (3)

fedorqui
fedorqui

Reputation: 290105

Parsing a JSON is not a very good idea (you should give a look to jq), but awk can help.

For example, you can check when user appears and, from there, act on the subsequent lines:

awk '/user/ {f=NR}
     NR==f+2 {sub ("Andreas","Mike")}
     NR==f+3 {sub (34, 29)}
     1' file

You can also provide the new values as parameters.

If you don't know the value of the parameters, use a regular expression to match the content inside:

awk '/user/ {f=NR} NR==f+2 {sub (/: ".*,$/,": \"Mike\",")} NR==f+3 {sub (/: [0-9]+$/, ": 29,")} 1' a

Test

$ awk '/user/ {f=NR} NR==f+2 {sub ("Andreas","Mike")} NR==f+3 {sub (34, 29)} 1' a
{
    "user" :
    {
        "name": "Mike",
        "age": 29
    },
    "viewer" :
    {
        "name": "Pedro",
        "age": 41
    }
}

Upvotes: 1

Ed Morton
Ed Morton

Reputation: 204164

sed is for simple substitutions on individual lines, that is all. For anything else you should be using awk.

$ cat tst.awk
BEGIN { split("name \"Mike\" age 29",map) }
/"user"/ { inUser = 1  }
inUser {
    for (i=1;i in map;i+=2) {
        if ($1 == "\""map[i]"\":") {
            sub(/: [^ ,]+/,": "map[i+1])
        }
    }
    if (/}/) {
        inUser = 0
    }
}
{ print }
$
$ awk -f tst.awk file
{
    "user" :
    {
        "name": "Mike",
        "age": 29
    },
    "viewer" :
    {
        "name": "Pedro",
        "age": 41
    }
}

The above will fail if the replacement string contains & since it's being used as the 2nd arg to sub() - if that could happen then you'd use match() and substr() instead of sub() so the replacement text is treated as a literal string:

    if ($1 == "\""map[i]"\":") {
        match($0,/: [^ ,]+/)
        $0 = substr($0,1,RSTART-1) ": "map[i+1] substr($0,RSTART+RLENGTH)
    }

Upvotes: 1

NeronLeVelu
NeronLeVelu

Reputation: 10039

sed -i '' -e '1h;1!H;$!d;x;s/\("user" :[^}]*"name": \)"[^"]*"\([^}]*"age": \)[0-9]*/\1"Mike"\234/' config.json

try this but cannot be sure there is not the same structure inside another one. It replace the first occurence

Upvotes: 1

Related Questions