相对于模式内的文本,跨多行模式之间的文本删除

相对于模式内的文本,跨多行模式之间的文本删除

我有一段文本块需要删除,但是,只有当它包含块内的特定文本时才需要删除:

...
<script language="JavaScript">
    var somethingA = 0;
    var somethingB = 0;
    var somethingC = 0;
    // do some stuff
</script>

<script language="JavaScript">
    var somethingA = 0;
    var somethingC = 0;
    var somethingD = 0;
    // do some stuff
</script>
....

我只想删除<script>包含的块。文件中任意位置的块var somethingB数量可能任意。<script>

我希望使用 sed 做类似的事情:

sed 's/<script/,/<\/script>/ D'

但是,我不知道如何仅删除var somethingB其中的块。

附言:我也可以使用 perl 或 awk。为了保持一致性,我宁愿使用 sed,但如果使用 perl 和/或 awk 更简单,我会很快换个方式。谢谢!

答案1

如果部分解决方案vim是可以接受的:

:%s/<script [^<]*\(\n[^<]*\)*somethingB.*\(\n[^<]*\)*<\/script>//g

但是如果其中还有其他标签,它将不起作用<script>,因为使用[^<],模式可能不包含<

答案2

我没有简单的解决方案。实际上,它使用 awk 以类似 C 的 awk 语言编写所需的算法。假设要过滤的文本位于名为“filename”的文件中:

awk 'BEGIN { curr=0 } \
     /<script .*>/ { in_block=1; del_block=0 } \
     /<\/script>/ { in_block=0; blockend=1 } \
     /var[[:space:]]+somethingB/ { if (in_block==1) \
                                     { del_block=1 } } \
    { if (in_block==0) \
        { if (blockend==0) \
            # Neither in a block nor block end reached.
            # Just print the line
            { print } \
          else \
            { # End of a block reached. Do block end handling
              # just this one time. Block end flag off
              blockend=0
              if (del_block==1) \
                { # delete the block. Just throw away the lines
                  # in the lines array
                  curr=0 } \
              else \
                { # End of block and no delete. Print it out
                  for (i=0; i<curr; i++) \
                    { print line[i] }
                    print   # Print the </script> line
                      # use line-array for the next block
                      curr=0 \
                } \
            } \
        } \
      else \
        { # In a block. Save the current line for later
          line[curr]=$0
          curr++ } \
    }' filename

(块的结束标记)的模式</script>有点简单。它要求它完全按照那样写,没有任何空格。如果它可以包含空格,您可能希望这样写:

/<[[:space:]]*\/script[[:space:]]*>/ 

的模式var somethingBvar- 一个或多个空格 - somethingB,这可能就是您要搜索的。如果您希望将其固定为恰好一个空格,var则更somethingB简单:/var somethingB/

答案3

这应该可以直接完成sed。由于我不是sed巫师,所以我需要运行两次。

  1. 在第一次运行中,我们准备文件以确保<script>...</script>块被空行包围:

    sed -e '/<script/i\ ' -e '/script>/a\ ' code.js
    

    这不是什么火箭科学:i 插入一条线a与模式匹配的行 附加一条线与模式匹配的行。在两种情况下,行都仅包含一个空白。

    这是需要sed单独检测每个块的,即非贪婪地在第二步中)。

  2. 第二次运行将消除其中的块var somethingB

    sed '/<script/,/script>/{H;d;};x;/var somethingB/d'
    
    • /<script/,/script>/{H;d;}将块移动到 sed 的保存空间(H附加到保存空间,d从模式空间中删除)
    • x将保存空间与模式空间进行交换
    • 如果模式/var somethingB/匹配,则删除(d)包含完整块的模式空间<script>
    • 最后sed隐式打印模式空间。

      我在这里引用的是Unix Sed 教程

  3. 因此,在一个命令行中使用一个很好的管道:

    sed -e '/<script/i\ ' -e '/script>/a\ ' code.js | sed '/<script/,/script>/{H;d;};x;/var somethingB/d'
    

    如果您愿意,可以使用第三个sed实例来摆脱多余的空行:

    sed '/^ $/d'
    

答案4

我不知道这是否是最有效的方法,但我设法用一行sed代码编写了它。假设您的文本块位于file.txt

tac file.txt | sed '/var somethingB/,/<script/{/var somethingB/p;d;}' | tac | sed '/var somethingB/,/<\/script>/d'

或者你甚至可以更加严格一点,然后执行以下操作:

tac file.txt | sed '/var somethingB = /,/^<script /{/var somethingB = /p;d;}' | tac | sed '/var somethingB = /,/^<\/script>$/d'

工作原理如下:

  1. 以相反的顺序读取文件:tac file.txt
  2. 在第一次运行中,删除模式(“var somethingB”)和块(“<script”)开头之间的行 – 记住以相反的顺序读取文件 –。但排除包含“var somethingB”的行(第二次运行需要它):sed '/var somethingB/,/<script/{/var somethingB/p;d;}'
  3. 再次反向读取文件(现在我们按正常顺序读取):tac
  4. 在第二次运行中,删除模式(“var somethingB”)和块末尾(“”)之间的行:sed '/var somethingB/,/<\/script>/d'。请注意,我们需要用 来转义字符 /。

相关内容