【问题标题】:How to delete a matching block once a pattern is matched模式匹配后如何删除匹配块
【发布时间】:2019-11-05 21:36:35
【问题描述】:

这是文件(命名为 sample.xml):


<?xml version="1.0" encoding="UTF-8"?>
<configs>

    <blah1 value="ma">
      <tag3>100MB</tag3>
    </blah1>

    <blah1 value="ba">
      <tag3>20MB</tag3>
    </blah1>

     <blah2 value="*" version="1.0" result="true">
        <blah1 value="xyz">
          <blah1 value="uvw" result="true">
             <tag>4</tag>
          </blah1>
        </blah1>
     </blah2>

  <!-- This is tag with def value -->
  <blah2 value="*" version="2.0" result="true">
    <blah1 value="abc">
      <blah1 value="def" result="true">
        <tag2>on</tag2>
      </blah1>
    </blah1>
  </blah2>

</configs>

在找到带有value="def" 的字符串时,删除从&lt;blah2&gt; to &lt;/blah2&gt; 标记开始的整个块

我不熟悉 sed 保持模式,但我从谷歌得到的东西非常接近

sed -n '/<blah2.*>/,/<\/blah2>/{
                                  H
                                  /<\/blah2>/ { 
                                        s/.*//;x
                                       /def/d
                                       p 
                                  }
                               }' sample.xml

预期结果:


<?xml version="1.0" encoding="UTF-8"?>
<configs>

    <blah1 value="ma">
      <tag3>100MB</tag3>
    </blah1>

    <blah1 value="ba">
      <tag3>20MB</tag3>
    </blah1>

     <blah2 value="*" version="1.0" result="true">
        <blah1 value="xyz">
          <blah1 value="uvw" result="true">
             <tag>4</tag>
          </blah1>
        </blah1>
     </blah2>

</configs>

实际结果(使用上述非工作 sed):

     <blah2 value="*" version="1.0" result="true">
        <blah1 value="xyz">
          <blah1 value="uvw" result="true">
             <tag>4</tag>
          </blah1>
        </blah1>
     </blah2>

【问题讨论】:

  • Don't Parse XML/HTML With Regex. 我建议使用 XML/HTML 解析器 (xmlstarlet, xmllint ...)。
  • def 总是标签/configs/blah2[2]/blah1/blah1 的属性吗?
  • 对,目前 xmlstarlet 在该主机(这是一些专有的 linux)上不可用,并且无法访问 Internet。我将不得不下载并将二进制文件移到那里。
  • 是的,def 始终是第二个 blah1 中的属性
  • xmlstarlet 的答案对您有帮助吗?

标签: xml shell sed xmlstarlet


【解决方案1】:

用 xmlstarlet 删除第二个标签 blah2

xmlstarlet edit --delete '//configs[blah2[2]/blah1/blah1[@value="def"]]/blah2[2]' file.xml

输出:

<?xml version="1.0" encoding="UTF-8"?>
<configs>
  <blah1 value="ma">
    <tag3>100MB</tag3>
  </blah1>
  <blah1 value="ba">
    <tag3>20MB</tag3>
  </blah1>
  <blah2 value="*" version="1.0" result="true">
    <blah1 value="xyz">
      <blah1 value="uvw" result="true">
        <tag>4</tag>
      </blah1>
    </blah1>
  </blah2>
</configs>

如果要就地编辑文件,请添加选项 -L。


使用XPath的说明:

//configs[blah2[2]/blah1/blah1[@value="def"]]/blah2[2]
|---A---| |-------------B------------------| |---C---|

A 和 B:您要查找的属性的路径

A和C:要删除的标签的路径

【讨论】:

  • 但是这个不检查值为“def”的标签。标签部分的数量可以更多也可以更少。
  • 太棒了!一个问题。可以使用 xmlstarlet 删除此标记部分上方的 xml 注释吗?
  • --delete '//configs/comment()'添加到xmlstarlet edit命令删除标签configs中的所有命令。
  • 谢谢@cyrus。由于我目前的限制,我只是选择 sed 答案作为可接受的解决方案。理想情况下,我想选择这两个答案作为接受的答案
  • @satya - 这是另一个(未经测试的)xpath 选项://blah2[.//blah1/@value='def']
【解决方案2】:

由于您对 sed 解决方案感到满意,考虑到您发布的示例输入/输出,这里有一个更好(更清晰、更便携等)的替代方案:

$ awk -v RS= -v ORS='\n\n' '!/value="def"/' file
<?xml version="1.0" encoding="UTF-8"?>
<configs>

    <blah1 value="ma">
      <tag3>100MB</tag3>
    </blah1>

    <blah1 value="ba">
      <tag3>20MB</tag3>
    </blah1>

     <blah2 value="*" version="1.0" result="true">
        <blah1 value="xyz">
          <blah1 value="uvw" result="true">
             <tag>4</tag>
          </blah1>
        </blah1>
     </blah2>

</configs>

如果这不是您所需要的,那么无论您需要什么,都可以使用更好的 awk 替代方案,因为 sed 仅最适合对单个字符串执行 s/old/new。

【讨论】:

  • 是否可以将注释行与块一起删除。注释可以是前面的单行,也可以是多行xml &lt;!-- This is tag with def value --&gt; or &lt;!-- This is a multi line tag for demo purpose --&gt;
  • 看看我发布的输出 - 它确实删除了注释行(看看&lt;!-- This is tag with def value --&gt; 是如何在输出中不存在的?)并且它是单行还是多行都没关系
  • 好的,我试过了,如果它们用换行符分隔,它就可以工作。如果我删除 blah2 块之间的换行符,或者如果我删除最后一个 blah2 块和配置之间的新行,它不起作用:(
  • @satya 我们所要做的就是您提供的示例输入。如果您的实际输入与示例输入的格式不同,那么您不应该期望任何不使用 XML 解析器的解决方案能够稳健地工作。如果您可以使用 XML 解析器,那么您应该这样做。如果您不能更新您的示例以真正反映您的文件格式,那么您可能可以获得 sed 或 awk 答案来处理该特定格式。
【解决方案3】:

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

sed '/<blah2.*>/{:a;N;/<\/blah2.*>/!ba;/value="def"/d}' file

如果一行包含&lt;blah2.*&gt;,则收集所有行,直到包含&lt;\/blah2.*&gt; 的行,然后测试这些行中的字符串value="def",如果找到,删除这些行。

【讨论】:

  • 一个问题是我们不能利用以前的标签部分值,如“ma”和“ba”。它们可能存在也可能不存在
  • @satya: b 是一个sed 命令。 :a 是一个名为 a 的标签。 /&lt;\/blah2.*&gt;/!ba:如果在模式空间中找不到&lt;\/blah2.*&gt;,则跳转到标签a。这是一个循环,直到找到 &lt;\/blah2.*&gt;
  • 哦!我的错。让我试试这个
  • 这很好用!我忘了提到的最后一件事,有时标签部分上方有评论。可以将其作为 sed 的一部分删除吗?如果没有,我可以将它作为另一个 sed 单独删除。
  • 如果注释行是&lt;blah2.*&gt; 行之前的行,则使用:sed 'N;/&lt;blah2.*&gt;/{:a;N;/&lt;\/blah2.*&gt;/!ba;/value="def"/d};P;D' file
猜你喜欢
  • 2021-03-13
  • 1970-01-01
  • 2012-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 1970-01-01
相关资源
最近更新 更多