【问题标题】:Deleting the block between two regex markers when a pattern is matched inside the block当块内的模式匹配时删除两个正则表达式标记之间的块
【发布时间】:2020-05-15 08:52:26
【问题描述】:

假设如下结构:

  -   key1: value11
      key2:
      - value21
      - value22
      - value23
      key3: value31
      key4:
      - value41
      - value42
      key5: value51
  -   key1: value12
      key2:
      - value24
      - value25
      key3: value32
      key5: value52
  -   key1: value13
      key2:
      - value26
      key3: value33
      key4:
      - value43
      - value44
      - value45
      key5: value53

是否可以删除(包括)开始和结束标记正则表达式之间的所有块:

 - begin marker: '^[[:blank:]]{2}-[[:blank:]]{3}key1:[[:blank:]].+$'
 - end marker:   '^[[:blank:]]{6}key5:[[:blank:]].+$'

当以下正则表达式在块内匹配时:

matching pattern: '^[[:blank:]]{6}key3:[[:blank:]]value32$'?

目标是获得:

  -   key1: value11
      key2:
      - value21
      - value22
      - value23
      key3: value31
      key4:
      - value41
      - value42
      key5: value51
  -   key1: value13
      key2:
      - value26
      key3: value33
      key4:
      - value43
      - value44
      - value45
      key5: value53

开始标记也可以用作结束标记,因为在块删除期间没有删除第二个标记。

我尝试了多种使用 sed/awk 的方法均未成功,例如这个受 post 的 4.21 段落启发的方法:

sed ':t
/^[[:blank:]]{2}-[[:blank:]]{3}key1:[[:blank:]].+$/,/^[[:blank:]]{6}key5:[[:blank:]].+$/ {      # For each line between these block markers
        /^[[:blank:]]{6}key5:[[:blank:]].+$/!{                                                  # If we are not at the /end/ marker
                $!{                                                                             # nor the last line of the file
                        N;                                                                      # add the Next line to the pattern space
                        bt
                }                                                                               # and branch (loop back) to the :t label
        }                                                                                       # This line matches the /end/ marker
        /^[[:blank:]]{6}key3:[[:blank:]]value32$/d;                                             # If /regex/ matches, delete the block
}' file

【问题讨论】:

    标签: awk sed pattern-matching block markers


    【解决方案1】:

    文件格式看起来像 YAML。那你为什么不用yq来过滤呢? 然后你可以说:

    yq -y '[ .[] | select (.key3 != "value32") ]' file
    

    结果:

    - key1: value11
      key2:
      - value21
      - value22
      - value23
      key3: value31
      key4:
      - value41
      - value42
      key5: value51
    - key1: value13
      key2:
      - value26
      key3: value33
      key4:
      - value43
      - value44
      - value45
      key5: value53
    

    您可能需要安装 yqpip install yq 或类似的东西。

    【讨论】:

    • 你说得对,这是一个 yaml 文件,yq 可能是该工作的完美候选人。由于当前未解决的问题太多(github.com/mikefarah/yq/issues),我之前驳回了 Mike Farah 的 yq,但是您指向的 yq(Andrey Kislyuk 的工具)似乎做得更好,值得考虑。它的用法看起来很简单;我只需要找到一种将它与变量值(示例中的 value32)一起使用的方法。
    【解决方案2】:

    sed 是对单个字符串执行s/old/new/ 的正确工具,仅此而已。对于任何更有趣的事情,您应该使用 awk 来获得清晰、可移植性、健壮性、效率等。

    鉴于您发布的示例输入/输出,您实际上并不需要您指定的第一个正则表达式,例如使用 GNU awk 进行多字符 RS 和 RT:

    awk -v RS='[[:blank:]]{6}key5:[[:blank:]][^\n]+\n' -v ORS= '
        !/\n[[:blank:]]{6}key3:[[:blank:]]value32\n/{ print $0 RT }
    ' file
      -   key1: value11
          key2:
          - value21
          - value22
          - value23
          key3: value31
          key4:
          - value41
          - value42
          key5: value51
      -   key1: value13
          key2:
          - value26
          key3: value33
          key4:
          - value43
          - value44
          - value45
          key5: value53
    

    或使用任何 awk:

    awk '
    { rec = rec $0 ORS }
    /^[[:blank:]]{6}key5:[[:blank:]].+$/ {
        if ( rec !~ /\n[[:blank:]]{6}key3:[[:blank:]]value32\n/ ) {
            printf "%s", rec
        }
        rec=""
    }
    ' file
      -   key1: value11
          key2:
          - value21
          - value22
          - value23
          key3: value31
          key4:
          - value41
          - value42
          key5: value51
      -   key1: value13
          key2:
          - value26
          key3: value33
          key4:
          - value43
          - value44
          - value45
          key5: value53
    

    但如果你愿意,你也可以使用第一个正则表达式,例如:

    awk '
    /^[[:blank:]]{2}-[[:blank:]]{3}key1:[[:blank:]].+$/ { inBlock=1 }
    inBlock { rec = rec $0 ORS }
    /^[[:blank:]]{6}key5:[[:blank:]].+$/ {
        if ( rec !~ /\n[[:blank:]]{6}key3:[[:blank:]]value32\n/ ) {
            printf "%s", rec
        }
        rec=""
        inBlock=0
    }
    ' file
      -   key1: value11
          key2:
          - value21
          - value22
          - value23
          key3: value31
          key4:
          - value41
          - value42
          key5: value51
      -   key1: value13
          key2:
          - value26
          key3: value33
          key4:
          - value43
          - value44
          - value45
          key5: value53
    

    【讨论】:

    • 我真的很喜欢第一个 awk 解决方案的简单性;您能解释一下为什么从正则表达式中删除了“^”以及 ORS awk 变量的格式吗?
    • 另外,为什么我不能用${var}替换匹配模式中的value32,用双引号替换简单引号?
    • ^ 表示“字符串开头”(有时被误称为“行首”,因为正在处理的字符串通常是单行,就像人们常说的 $ 表示“行尾”,但不是,它的意思是“字符串结束”)。有问题的字符串是从- key1: 行开始的多行文本块,因此在它的开头查找key3: 行是错误的,它位于它的中间。使用ORS=,我将ORS 设置为空字符串,因此awk 不会在我的print 语句之后添加换行符,因为我已经将换行符打印为RT 的一部分。
    • Awk 不是 shell,它是一个完全独立的工具,有自己的语法、语义和上下文。 ${var} 是在 shell 中获取 shell 变量值的方式——在从 shell 调用的 awk 脚本中,你不能在从 shell 调用的 C 程序中那样做。除非您需要双引号,否则应始终将 shell 脚本和字符串括在单引号中,请参阅 mywiki.wooledge.org/Quotes 了解引号在 shell 中的工作原理,并参阅 stackoverflow.com/q/19075671/1745001 了解如何在awk 脚本。
    • awk -v val="$var" '... if (rec !~ ("\n[[:blank:]]{6}key3:[[:blank:]]" val "\n") ) ...
    【解决方案3】:

    这可能对你有用(GNU sed):

    sed -E '/^\s{2}-\s{3}key1:\s/{:a;N;/^\s{6}key5:\s/M!ba;/^\s{6}key3:\svalue32$/Md}' file
    

    收集key1key5 之间的一组行,如果该组包含所需的字符串,则删除整个组。

    注意使用M 标志,允许多行匹配。

    本质上:

    sed '/key1/{:a;N;/key5/!ba;/key3.*value32$/Md}' file
    

    【讨论】:

      【解决方案4】:

      如果你真的想要 sed,你可以将范围存储在保持空间中,然后当且仅当它不包含要排除整个范围的字符串时打印保持空间:

      /^[[:blank:]]{2}-[[:blank:]]{3}key1:[[:blank:]].+$/,/^[[:blank:]]{6}key5:[[:blank:]].+$/{
         /^[[:blank:]]{2}-[[:blank:]]{3}key1:[[:blank:]].+$/h
         //!H
         /^[[:blank:]]{6}key5:[[:blank:]].+$/{
           g
           /\n[[:blank:]]{6}key3:[[:blank:]]value32\n/!p
         }
         d
      }
      

      以上必须使用sed -Ef cmdfile file运行。

      其中一个烦恼就是必须重复这些模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-30
        • 2018-09-02
        • 2018-06-28
        相关资源
        最近更新 更多