如何从文件中删除文本块

时间:2014-07-19 01:14:06

标签: macos bash sed

编辑:之前没有提及要在OS X中执行

我正在尝试创建一个bash脚本,它将从文件中删除一些块并将结果保存到另一个块。

我要过滤的文件内容应如下所示:

<element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
</element>
<element>
    <subElement name="removeme"/>
    <subElement name="removeme"/>
    <subElement name="removeme"/>
</element>
<element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
</element>

我要移除的是包含<element></element>标记的群组,其中包含子元素<subElement name="removeme"/>

保证没有任何组合将“removeme”和“leaveme”元素混合在一起。

我知道如何使用这样的正则表达式执行此操作:

<element>(?:(?!/elem).)*"removeme".*?</element>

但我真的迷失了如何在shell脚本中做到这一点,找到了一些关于sed的信息,但却不明白如何完成它。

感谢。

4 个答案:

答案 0 :(得分:3)

Regular expressions are certainly the wrong tool to parse XML。您希望XML处理工具删除与xpath //element[subElement[@name="removeme"]]

匹配的节点
  • elementsubElement子节点,其name属性值为removeme

使用xmlstarlet

xmlstarlet ed -d '//element[subElement[@name="removeme"]]' << ENDXML
<elements>
   <element>
      <subElement name="leaveme"/>
      <subElement name="leaveme"/>
      <subElement name="leaveme"/>
   </element>
   <element>
      <subElement name="removeme"/>
      <subElement name="removeme"/>
      <subElement name="removeme"/>
   </element>
   <element>
      <subElement name="leaveme"/>
      <subElement name="leaveme"/>
      <subElement name="leaveme"/>
   </element>
</elements>
ENDXML
<?xml version="1.0"?>
<elements>
  <element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
  </element>
  <element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
  </element>
</elements>

答案 1 :(得分:1)

以下内容(基于Jotne的帖子here)是收集lines数组中文件的所有行。 <element></element>代码的位置分别保存在i_starti_end中。如果看到<subElement name="removeme"/>,则found设置为1(true)。如果i_end为真,则0有条件地设置为found,如果found不为真,则{ - 1}}设置为结束元素的行号(数组索引)。如果i_end不为零,则打印开始和结束标记之间的块。

awk '
  { lines[NR] = $0 }
  /<element>/   { i_start = NR }
  /<\/element>/ { i_end = found ? 0 : NR; found = 0 }
  /<subElement name="removeme"\/>/ { found = 1 }
  i_end {
    for (i = i_start; i <= i_end; i++)
      print lines[i]
    i_end = 0;
  }
' file

答案 2 :(得分:1)

使用gnu awk你可以这样做:

awk -v RS="<element>" '!/removeme/ && NR>1{print RS $0}' file
<element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
</element>

<element>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
    <subElement name="leaveme"/>
</element>

通过将RS设置为<element>,您告诉awk以阻止模式工作,并以<element>开头 然后,!/removeme/告诉awk不要使用removeme数据打印该块。

答案 3 :(得分:0)

使用sed:

sed -n '
    /<element>/h
    /<element>/!H
    /<\/element>/{g;/<subElement name="removeme"\/>/!p;}
' file

/<element>/h命令初始化匹配保留空间和模式空间内容。

如果行与/<element>/!H不匹配,<element>命令会将模式空间内容附加到保留空间。

/<\/element>/{g;/<subElement name="removeme"\/>/!p}命令测试结束标记和匹配执行后续两个命令:

  1. 将填充的保留空间复制到模式空间。现在,正则表达式将再次测试包含整个element块的更新模式空间。
  2. 正则表达式查找过滤子元素值;在不匹配的情况下,模式空间被打印出来。