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

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

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

我想做的是在

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

"user" 块内的大括号之间的两行具有自己的值以获得结果:

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

简单搜索包含 "name" 或 "age" 的行将不起作用,因为它们可能属于另一个结构,不应修改。

通过结合几个示例,我发现我可以使用这个示例:

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

然而它似乎相当复杂,这是我的问题。

  1. 如何修改匹配模式以仅检测括号之间的内容,这样我就不必重新创建 "user" 键。
  2. 还有其他更优雅的方案吗?欢迎使用 sed、awk 或 OS X 中包含的任何其他系统工具。

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

例如,您可以检查 user 何时出现,并从那里开始对后续行进行操作:

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

您还可以提供新值作为参数。

如果不知道参数的值,用正则表达式匹配里面的内容:

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

测试

$ 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
    }
}
sed -i '' -e '1h;1!H;$!d;x;s/\("user" :[^}]*"name": \)"[^"]*"\([^}]*"age": \)[0-9]*/"Mike"4/' config.json

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

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 ( == "\""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
    }
}

如果替换字符串包含 &,上面将失败,因为它被用作 sub() 的第二个参数 - 如果可能发生,那么您将使用 match()substr() 而不是 sub() 因此替换文本被视为文字字符串:

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