如何用sed、awk或其他OS X工具替换文件中的多行块(JSON格式)?

时间:2021-09-09 16:48:08

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.

我正在寻找在终端中执行的一行程序,以在文本文件中使用我自己的上下文替换多行文本块。我使用的是OSX(不是GNU sed),无法安装任何附加工具。

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”块内的花括号之间的两行具有自己的值以获得结果:

{
    "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.

简单地搜索包含“name”或“age”的行是行不通的,因为它们可以属于另一个结构,不应该修改。

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

i ' -n $'1h;1!H;$ {;g;s #“用户”[^ {]*[^ }]* #“用户”:\ \ \ n \ \ \ t { \ \ \ n \ \ \ \ \ \ t“名称”:“迈克”,\ \ \ n \ \ \ \ \ \ t“年龄”:29 \ \ \ n \ \ \ t # p;}的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. 如何修改匹配模式,只检测括号之间的内容,这样我就不必重新创建“user”键。
  3. Is there another more elegant solution? sed, awk or any other system tools inlcuded in OS X are welcome.
  4. 还有其他更优雅的解决方案吗?sed、awk或任何其他在OS X中嵌入的系统工具都是受欢迎的。

3 个解决方案

#1


1  

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

解析JSON不是一个好主意(您应该看看jq),但是awk可以有所帮助。

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
    }
}

#2


1  

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

尝试一下,但是不能确定在另一个结构中没有相同的结构。它取代了第一次出现

#3


1  

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

sed只表示单行上的简单替换,仅此而已。对于其他任何你应该使用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:

如果替换字符串包含&因为它被用作第二个arg to sub()——如果可能发生,那么您将使用match()和substr()代替sub(),因此替换文本被当作一个字符串:

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

#1


1  

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

解析JSON不是一个好主意(您应该看看jq),但是awk可以有所帮助。

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
    }
}

#2


1  

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

尝试一下,但是不能确定在另一个结构中没有相同的结构。它取代了第一次出现

#3


1  

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

sed只表示单行上的简单替换,仅此而已。对于其他任何你应该使用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:

如果替换字符串包含&因为它被用作第二个arg to sub()——如果可能发生,那么您将使用match()和substr()代替sub(),因此替换文本被当作一个字符串:

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