【问题标题】:How to replace a multiline block from a file (JSON format) with sed, awk or other OS X tools?如何用 sed、awk 或其他 OS X 工具替换文件(JSON 格式)中的多行块?
【发布时间】:2015-08-08 04:08:18
【问题描述】:

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

我想要做的是替换

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

“用户”块内的大括号之间的两行带有自己的值以获得结果:

{
    "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. 如何修改匹配模式以仅检测括号之间的内容,因此我不必重新创建“用户”键。
  2. 还有其他更优雅的解决方案吗?欢迎使用 sed、awk 或 OS X 中包含的任何其他系统工具。

【问题讨论】:

    标签: regex awk sed osx-yosemite


    【解决方案1】:

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

    【讨论】:

    • 也许,一些关于正则表达式触发器的安全性,如/^[[:blank:]]*"user" :/(对于子分隔符也是如此)。也使用NR 值而不是递减的ff=NRNR == f + 2)更容易理解(对我来说)。不错的在线人,假设在这种情况下很难做到无懈可击。 (需要重定向才能将信息写入自身,例如示例中的sed -i
    • jq 很好,但不是 OS X 的一部分。重新定义 awk:如果我真的不知道当前值而只知道键怎么办?
    • @NeronLeVelu 很好的建议,谢谢!我也不喜欢f-- 方法,这看起来更好:)
    • @Gamadril 然后使用正则表达式查看我的更新以匹配它们。
    • sub (34, 29) 会将1234 更改为1229。我怀疑 OP 并不是真的只想更改他的示例中列出的特定值。 sub (/: ".*,$/,": \"Mike\"") 将删除行尾的 ,
    【解决方案2】:
    sed -i '' -e '1h;1!H;$!d;x;s/\("user" :[^}]*"name": \)"[^"]*"\([^}]*"age": \)[0-9]*/\1"Mike"\234/' config.json
    

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-23
        • 1970-01-01
        • 1970-01-01
        • 2020-12-15
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        • 2021-07-24
        相关资源
        最近更新 更多