在 Linux 命令行下评估 XPath 表达式
1. 概述
当我们使用XML 时,我们可以使用XPath 来浏览 XML 文档中的元素和属性。 在本教程中,我们将讨论如何在 Linux 命令行下计算 XPath 表达式。
2. 我们的 XML 示例和 XPath 表达式
首先,让我们创建一个 XML 文档books.xml,作为我们将在本教程中使用的输入 XML 文件:
<books>
<book id="1" category="linux">
<title lang="en">Linux Device Drivers</title>
<year>2003</year>
<author>Jonathan Corbet</author>
<author>Alessandro Rubini</author>
</book>
<book id="2" category="linux">
<title lang="en">Understanding the Linux Kernel</title>
<year>2005</year>
<author>Daniel P. Bovet</author>
<author>Marco Cesati</author>
</book>
<book id="3" category="novel">
<title lang="en">A Game of Thrones</title>
<year>2013</year>
<author>George R. R. Martin</author>
</book>
<book id="4" category="novel">
<title lang="fr">The Little Prince</title>
<year>1990</year>
<author>Antoine de Saint-Exupéry</author>
</book>
</books>
在我们的books.xml文件中,我们有四本书。稍后,我们将讨论如何在 Linux 命令行下评估几个 XPath 表达式:
- //title[@lang=‘fr’] – 这个 XPath 表达式选择所有用法语编写的书籍的标题元素(“ book ”元素有一个“ lang ”属性,值为“ fr ”)
- //book[year>2004]/title – 如果出版年份晚于 2004 年,此 XPath 表达式将选择所有书名元素(“ year ”元素的值大于2004)
在本教程中,我们将讨论在命令行下使用 XPath 的三种不同方法:
- 使用*xmllint *命令
- 使用XMLStarlet 工具包
- 使用xidel 实用程序
3. 使用xmllint命令
xmllint命令与xmllib2 软件包*一起安装。 *通常,我们可以使用此命令来验证 XML 文件、解析 XML 文件或漂亮地打印 XML 文件。
xmllint命令支持“ -xpath *”*选项来评估 XPath 表达式:
xmllint --xpath "XPATH_EXPRESSION" INPUT.xml
值得一提的是,由于 xmllib2仅实现XPath 1.0 ,xmllint命令仅支持 XPath 1.0。
让我们用我们的 XPath 表达式进行测试,看看我们是否能得到预期的结果。
首先,让我们在books.xml中选择所有英文书籍的标题元素:
$ xmllint --xpath "//title[@lang='fr']" books.xml
<title lang="fr">The Little Prince</title>
我们在输出中得到了《小王子》这本书的标题元素。这是正确的,因为它是唯一具有*lang=”fr”*属性的标题元素。
其次,让我们测试另一个 XPath 表达式:
$ xmllint --xpath "//book[year>2004]/title" books.xml
<title lang="en">Understanding the Linux Kernel</title>
<title lang="en">A Game of Thrones</title>
这一次,xmllint打印两个标题元素。我们的第二个 XPath 表达式也由xmllint命令正确评估。
4. 使用 XMLStarlet 工具包
XMLStarlet 是一个基于libxml2 的强大的命令行 XML 工具包。因此,与xmllint命令类似,XMLStartlet仅支持 XPath 1.0。
XMLStartlet 附带一个名为 xml的可执行文件,我们可以将其用作 xmlstarlet命令的缩写形式。
4.1.XMLStarlet 语法
xml命令的语法是:
xml [options] <command> [command options]
XMLStarlet 定义了一组命令来执行不同的 XML 操作——例如,ed ( edit ) 编辑或更新 XML 文档, tr ( transform ) 使用 XSLT 转换 XML 文档,等等。
**要使用 XPath 选择数据或查询 XML 文档,我们可以使用sel ( select ) 命令。**事实上, sel命令可以做的远不止 XPath 表达式求值。
基本上,sel命令允许我们避免编写 XSLT 样式表来执行一些 XML 文档查询。它可以通过命令行选项的组合为我们生成 XSLT。
也就是说,当我们使用 sel命令时,XMLStarlet 会将我们所有的命令参数转换为 XSLT 来对输入的 XML 文档进行查询。
让我们看一下sel命令的一般语法:
xml sel -t <template options> Input.xml
XSLT 模板 是 XSLT 的一个基本概念。使用sel命令,我们使用-t*选项创建模板。
在本教程中,我们不会深入研究 XSLT 转换。我们的目标是评估 XPath 表达式。
sel命令支持许多模板选项。我们将介绍其中的两个:-c和*-v*,因为这两个模板选项非常常用于 XPath 评估。
例如,假设 XPath 表达式的评估结果是*text*:
- -c “ XPath_Expression”选项将应用“ xsl:copy-of ” - 这会创建找到节点的副本,因此我们将获得:text
- -v “ *XPath_Expression”*选项将应用“ xsl:value-of ”——这会在结果中提取 XML 元素的值,所以我们将有:text
4.2. 使用xml sel命令评估 XPath 表达式
现在,让我们尝试使用我们的两个 XPath 表达式。
首先,我们将使用带有*-c模板选项的xml sel*命令测试我们的第一个 XPath 表达式:
$ xml sel -t -c "//title[@lang='fr']" books.xml
<title lang="fr">The Little Prince</title>
如输出所示,我们的 XPath 表达式已被正确评估,并且我们得到了预期的title元素。 接下来,让我们看看如果我们使用 -v模板选项会得到什么:
$ xml sel -t -v "//title[@lang='fr']" books.xml
The Little Prince
这一次,我们得到了没有 XML 标记的title元素的文本。
现在,让我们用我们的其他 XPath 表达式测试该命令:
$ xml sel -t -c "/books/book[year>2004]/title" books.xml
<title lang="en">Understanding the Linux Kernel</title><title lang="en">A Game of Thrones</title>
当我们使用*-c选项时,输出包含两个预期的title*元素。
但是,输出不是“漂亮的”XML元素之间的换行符不知何故被吞没了。
发生这种情况是因为元素之间的换行符被视为空格,这意味着**<xsl:copy-of>指令将删除元素之间的所有空格**。
接下来,让我们看看使用*-v*选项会得到什么:
$ xml sel -t -v "/books/book[year>2004]/title" books.xml
Understanding the Linux Kernel
A Game of Thrones
如输出所示,当我们使用 -v选项时,我们将获得匹配元素的文本,每个值位于单独的行中。
这一次,不删除换行符。这是因为当结果有多个元素时,*xsl:value-of 将位于xsl:for-each *元素中,类似于:
<xsl:for-each select="/books/book[year>2004]/title">
<xsl:value-of select="."/>
</xsl:for-each>
因此,每个匹配元素的文本将打印到单独的行。
5. 使用xidel命令
xidel 命令是一个很好的 XML/HTML/JSON 数据提取实用程序,并且支持XPath 3.0 。
使用带有 XPath 表达式的xidel命令提取数据 非常简单:
xidel [options] --xpath "XPath Expression" XML_INPUT
我们可以传递一些选项来控制输出,我们将在后面的示例中看到。
5.1. 使用 XPath 表达式提取数据
让我们尝试使用第一个 XPath 表达式的xidel命令:
$ xidel --xpath "//title[@lang='fr']" books.xml
Retrieving: books.xml
Processing: books.xml
The Little Prince
正如我们在输出中看到的,xidel 默认打印状态信息。此外,它会自动从找到的元素中提取文本。
如果我们想跳过状态消息,我们可以添加 -s选项让xidel在“静默”模式下工作。
此外,我们可以 通过传递–printed-node-format=”xml”选项让xidel*打印完整的 XML 元素*:
上面的截图显示了xidel命令的一个很好的特性:当xidel输出为 XML 格式时,它会突出显示控制台输出中的属性。
接下来,让我们使用第二个 XPath 表达式执行xidel命令:
$ xidel -s --printed-node-format="xml" --xpath "/books/book[year>2004]/title" books.xml
<title lang="en">Understanding the Linux Kernel</title>
<title lang="en">A Game of Thrones</title>
正如我们所料,它从我们的示例文件中打印出两个标题元素。
5.2. 评估 XPath 3.0 表达式
最后,让我们测试一下 xidel命令是否可以与 XPath 3.0 表达式一起使用。 序列数据类型 从XPath 3.0 开始就出现了。因此,如果图书的出版年份在给定的值序列中,我们将使用序列数据类型编写 XPath 表达式来打印图书元素://book[year=(2004, 2005, 2013, 2020)]
让我们看看xidel是否可以评估这个 XPath 表达式并找到我们感兴趣的书籍:
$ xidel -s --printed-node-format="xml" --xpath "//book[year=(2004, 2005, 2013, 2020)]" books.xml
<book id="2" category="linux">
<title lang="en">Understanding the Linux Kernel</title>
<year>2005</year>
<author>Daniel P. Bovet</author>
<author>Marco Cesati</author>
</book>
<book id="3" category="novel">
<title lang="en">A Game of Thrones</title>
<year>2013</year>
<author>George R. R. Martin</author>
</book>
太好了,它适用于 XPath 3.0 表达式!
由于xmllint和 XMLStarlet 仅支持 XPath 1.0,它们无法计算此 XPath 表达式:
$ xmllint --xpath "//book[year=(2004, 2005, 2013, 2020)]" books.xml
XPath error : Invalid expression
//book[year=(2004, 2005, 2013, 2020)]
^
XPath evaluation failure
$ xml sel -t -c "//book[year=(2004, 2005, 2013, 2020)]" books.xml
Invalid expression: //book[year=(2004, 2005, 2013, 2020)]
compilation error: element copy-of
xsl:copy-of : could not compile select expression '//book[year=(2004, 2005, 2013, 2020)]'