Bash - 如果子节点的属性值不等于特定值,则删除 XML 节点?

Ant*_*sov 2 xml linux bash xmlstarlet

我有 RSS 提要,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>my feed</title>
  <link rel="self" href="http://myhomesite.com/articles/feed/"/>
  <updated>2019-11-04T12:45:00Z</updated>
  <id>http://myhomesite.com/articles/feed/?dt=2019-11-04T12:45:00Z</id>
  <entry>
    <id>id0</id>
    <link rel="alternate" type="text/html" href="https://yandex.ru/link123"/>
    <author>
      <name/>
    </author>
    <published>2019-11-04T12:45:00Z</published>
    <updated>2019-11-04T12:45:00Z</updated>
    <title type="html"><![CDATA[foo bar foo bar]]></title>
    <content type="html"><![CDATA[]]></content>
  </entry>
  <entry>
    <id>id2</id>
    <link rel="alternate" type="text/html" href="https://myhomesite.com"/>
    <author>
      <name/>
    </author>
    <published>2019-11-04T09:45:00Z</published>
    <updated>2019-11-04T09:45:00Z</updated>
    <title type="html"><![CDATA[foo bar foo bar]]></title>
    <content type="html"><![CDATA[]]></content>
  </entry>
....
Run Code Online (Sandbox Code Playgroud)

我想删除link href !=/feed/entry处的所有节点 ( ) 。 http://myhomesite.com

如何使用 Bash 删除值从指定符号开始的 XML 节点?

Rob*_*obC 5

Bash 功能本身不太适合解析 XML。

这个著名的Bash FAQ声明如下:

不要尝试使用[从 XML 文件中提取数据] (这会导致不期望的结果)。

考虑使用 XML 特定的命令行工具,例如XMLStarlet。如果您尚未安装 XML Starlet,请参阅此处的下载信息。


解决方案:

使用 XML Starlet,您可以运行以下命令将所需的结果输出到终端:

xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss
Run Code Online (Sandbox Code Playgroud)

注意:/path/to/file.rss上面显示的命令末尾的部分应替换为实际文件的真实路径名.rss

解释:

上述命令的各个部分细分如下:

  • xml- 调用 XML Starlet 命令。

  • ed- 编辑/更新 XML 文档。

  • -N x="http://www.w3.org/2005/Atom"- 该-N选项将名称空间(即)绑定http://www.w3.org/2005/Atom到我们任意命名的前缀x

  • -d- 删除匹配的节点。

  • '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]'表达式用于查找/匹配问题中指定的适当节点。

    链接 href != 的所有节点 (/feed/entry) http://myhomesite.com

    正如您所看到的,在 XPath 表达式中,我们在x元素节点名称前面添加前缀,即x:entryx:link,以确保我们在正确的命名空间中寻址元素。

  • /path/to/file.rss- 源.rss文件的路径名。

保存生成的 XML (RSS)

要保存生成的 XML,您可以:

  1. 将选项添加--inplace到上述命令 - 这将.rss用所需的结果覆盖原始命令。例如:

     xml ed --inplace -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss
    
    Run Code Online (Sandbox Code Playgroud)
  2. 或者,使用重定向运算符( >) 并指定保存输出的位置的路径名。例如,以下复合命令会将结果保存到新文件中:

     xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss > /path/to/results.rss
    
    Run Code Online (Sandbox Code Playgroud)

    注意:上述复合命令末尾/path/to/results.rss的 应该替换为要保存新文件的真实路径名。

XPath 具有local-name()

鉴于您的示例源 XML (RSS) 不包含任何QName,因此也可以利用 XPath 的local-name()功能。这将不需要使用 XMLStarlet 的-N选项来绑定命名空间。例如:

xml ed -d '//*[local-name() = "entry" and not(child::*[local-name() = "link"][@href="https://myhomesite.com"])]' /path/to/file.rss
Run Code Online (Sandbox Code Playgroud)

重要提示:可能xml需要替换本文中显示的所有示例命令中的前导部分xmlstarlet。例如:

xmlstarlet ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss.
^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

编辑:

鉴于您的示例 XML,还可以对默认名称空间使用简化的语法,即_:使用x:. 通过使用下划线 ( _),您无需使用-N将名称空间绑定到前缀的选项。请参阅标题为 1.3 的部分XMLStarlet 文档中的更方便的解决方案可获取有关此功能的更多信息。

例如:

xml ed -d '//_:entry[not(child::_:link[@href="https://myhomesite.com"])]' /path/to/file.rss
Run Code Online (Sandbox Code Playgroud)

为了进一步了解当源 XML 使用命名空间时如何使用 XMLStarlet,我建议您还阅读文档中的命名空间和默认命名空间。


编辑2:

OP的作者随后在评论中写道:

还有一个问题。条件[not(child::_:link[@href="myhomesite.com"])]很严格。我想要像 start withmyhomesite.com但 URI 不重要,即myhomesite.com**anything**。这是可能的?[原文如此]

像这样的东西..xmlstarlet ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[matches(@href, '^https://myhomesite.com/' )]/@href)]' feed.rs

考虑将 Xpath 的starts-with()函数与前面给出的任一示例结合使用。例如:

  • 使用-N选项 和starts-with()

    xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[starts-with(@href, "https://myhomesite.com")])]' file.rss
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用local-name()starts-with()

    xml ed -d '//*[local-name() = "entry" and not(child::*[local-name() = "link"][starts-with(@href, "https://myhomesite.com")])]' file.rss
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用默认命名空间的简化语法,即下划线和starts-with()

    xml ed -d '//_:entry[not(child::_:link[starts-with(@href, "https://myhomesite.com")])]' file.rss
    
    Run Code Online (Sandbox Code Playgroud)