【问题标题】:How to remove blocks of text from file如何从文件中删除文本块
【发布时间】:2014-07-19 18:59:13
【问题描述】:

编辑:之前没有提到这是要在 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>

我要删除的是包含 &lt;element&gt;&lt;/element&gt; 标签的组,其中包含子元素 &lt;subElement name="removeme"/&gt;

保证任何组都不会混合“removeme”和“leaveme”元素。

我知道如何使用这样的正则表达式来做到这一点:

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

但我真的不知道如何在 shell 脚本中执行此操作,找到了一些关于 sed 的信息,但不知道如何完成。

谢谢。

【问题讨论】:

  • sed 不太适合这项任务。请改用awk。看看 Jotne 的答案(或者可能是我的)here。它基本上与你想要的相反,但你应该能够适应它。
  • 我确实看过但它只是使用一些分隔符来定义删除的内容,我需要知道内容是否包含特定文本以确定是否删除它,是否可以调整它?
  • 它同时使用分隔符(如您的&lt;element&gt; 标签)和内容。我认为这很容易适应。我会尝试一下,如果它不适用,我会告诉你,但我认为它是。
  • 好的,非常感谢:D
  • 另请注意格伦杰克曼的回答,这确实更合适,绝对更防弹。

标签: macos bash sed


【解决方案1】:

Regular expressions are certainly the wrong tool to parse XML。您想要一个 XML 处理工具来删除与 xpath //element[subElement[@name="removeme"]] 匹配的节点

  • element 具有 subElement 子节点的节点,该子节点的 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>

【讨论】:

  • 试过了,但在 os x bash 上没有找到 xmlstarlet,有什么替代品吗?
  • @Gusman 你需要安装xmlstarlet
  • 这是正确的方法,但大多数系统默认没有安装它,并不是所有用户都有权添加另一个工具。
【解决方案2】:

下面的想法(基于 Jotne 的帖子here)是将文件的所有行收集到lines 数组中。 &lt;element&gt;&lt;/element&gt;标签的位置分别保存在i_starti_end中。如果看到&lt;subElement name="removeme"/&gt;,则found 设置为1 (true)。如果found 为真,则i_end 有条件地设置为0,如果found 不为真,则设置为结束元素的行号(数组索引)。如果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

【讨论】:

    【解决方案3】:

    使用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 设置为&lt;element&gt;,您是在告诉awk 在块模式下工作,它以&lt;element&gt; 开头
    然后!/removeme/ 告诉awk 不要打印带有removeme 数据的块。

    【讨论】:

      【解决方案4】:

      使用 sed:

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

      /&lt;element&gt;/h 命令在将保持空间与模式空间内容匹配时初始化。

      如果行与&lt;element&gt; 不匹配,/&lt;element&gt;/!H 命令会将模式空间内容附加到保持空间。

      /&lt;\/element&gt;/{g;/&lt;subElement name="removeme"\/&gt;/!p} 命令测试结束标记,匹配时执行两个后续命令:

      1. 已填充的保留空间被复制到模式空间。现在正则表达式针对包含整个element 块的更新模式空间进行测试。
      2. 正则表达式查找过滤子元素值;如果不匹配,则打印模式空间。

      【讨论】:

        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 2018-11-08
        • 2016-06-07
        • 2023-04-04
        • 1970-01-01
        • 2011-11-13
        • 2017-04-13
        • 1970-01-01
        相关资源
        最近更新 更多