如何在Bash中解析XML?

169

理想情况下,我希望能够做到以下几点:

cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt

2
将以下与编程相关的内容从英文翻译成中文。只返回翻译后的文本:http://unix.stackexchange.com/questions/83385/parse-xml-to-get-node-value-in-bash-script || http://superuser.com/questions/369996/scripting-what-is-the-easiest-to-extact-a-value-in-a-tag-of-a-xml-file - Ciro Santilli OurBigBook.com
输出 Example 的命令是:echo '<html><head><title>Example</title></body></html>' | yq -p xml '.html.head.title'。参见链接:yq一些例子 - jpseng
echo '<html><head><title>Example</title></body></html>' | yq -p xml '.html.head.title' 输出 Example。参见:yq一些示例 - undefined
17个回答

3

好的,您可以使用xpath实用程序。我猜perl的XML :: Xpath包含它。


2
虽然有一些现成的控制台工具可以完成你想要的功能,但是使用通用编程语言(如Python)编写几行代码可能需要更少的时间,并且容易扩展和适应你的需求。
这是一个使用 lxml 解析的 Python 脚本——它将文件名或 URL 作为第一个参数,XPath 表达式作为第二个参数,并打印与给定表达式匹配的字符串/节点。

示例1

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]

#  a hack allowing to access the
#  default namespace (if defined) via the 'p:' prefix    
#  E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
#  an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
    ns['p'] = ns.pop(None)
#   end of hack    

for e in tree.xpath(xpath_expression, namespaces=ns):
    if isinstance(e, str):
        print(e)
    else:
        print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))

lxml可以通过pip install lxml进行安装。在Ubuntu上,您可以使用sudo apt install python-lxml

用法

python xpath.py myfile.xml "//mynode"

lxml 也接受 URL 作为输入:

python xpath.py http://www.feedforall.com/sample.xml "//link"

注意: 如果您的XML具有没有前缀的默认命名空间(例如xmlns=http://abc...),则您必须在表达式中使用p前缀(由“hack”提供),例如//p:modulepom.xml文件获取模块。如果p前缀已在您的XML中映射,则需要修改脚本以使用另一个前缀。


示例2

这是一个一次性脚本,仅用于从apache maven文件中提取模块名称。请注意,节点名称(module)带有默认命名空间{http://maven.apache.org/POM/4.0.0}的前缀:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modules>
        <module>cherries</module>
        <module>bananas</module>
        <module>pears</module>
    </modules>
</project>

module_extractor.py:

from lxml import etree
for _, e in etree.iterparse(open("pom.xml"), tag="{http://maven.apache.org/POM/4.0.0}module"):
    print(e.text)

这非常棒,无论是想要避免安装额外的软件包还是没有权限访问,都能够使用。在构建机器上,我可以通过额外的 pip install 来替代 apt-getyum 命令。谢谢! - E. Moffat

2
虽然“不要在bash中没有适当的工具的情况下解析XML、JSON等数据”似乎是一个好建议,但我不同意。如果这是一项兼职工作,寻找适当的工具并学习它是浪费时间的……Awk可以在几分钟内完成。我的程序必须处理所有上述以及更多种类的数据。我不想测试30个工具来解析我需要的5-7-10个不同格式的数据,如果我可以在几分钟内用awk解决问题,那就太好了。我不关心XML、JSON或其他任何东西!我需要一个单一的解决方案。

例如:我的智能家居程序运行我们的家庭。在此过程中,它会读取大量不同格式的数据,我无法控制。我从不使用专门的、适当的工具,因为我不想花费超过几分钟的时间来阅读我需要的数据。通过FS和RS调整,这个awk解决方案对于任何文本格式都可以完美地工作。但是,当您的主要任务是在该格式中处理大量数据时,这可能不是正确的答案!

我昨天遇到了从bash解析XML的问题。以下是我为任何分层数据格式执行的操作。作为奖励——我直接将数据分配给bash脚本中的变量。

为了让事情更容易阅读,我将分几个阶段介绍解决方案。从OP测试数据中,我创建了一个文件:test.xml

在bash中解析XML并提取其中的数据,每行限制在90个字符:

awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml

我通常使用更易读的版本,因为在实际生活中我经常需要进行不同的测试,这样更容易进行修改:

awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml

我不在乎格式的称呼,只寻求最简单的解决方案。在这种情况下,我可以从数据中看到换行符是记录分隔符(RS),<>是字段分隔符(FS)。在我的原始情况下,我需要在两个记录中对6个值进行复杂的索引,并将它们关联起来,查找数据存在的时间以及可能存在或不存在的字段(记录)。使用awk只需要4行代码就能完美地解决这个问题。因此,在使用之前,请根据每个需求调整想法!
第二部分只是查看行(RS)是否有所需字符串,如果有,则打印所需字段(FS)。上述操作花费了我大约30秒钟从上次使用此方式的最后一个命令中复制和调整而来(时间是其4倍长)。就是这样!90个字符就完成了。
但是,我总是需要将数据整齐地放入脚本变量中。我首先这样测试结构:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml

在某些情况下,我使用printf代替print。当我看到一切都很好时,我简单地完成了对变量的赋值。我知道许多人认为“eval”是“邪恶”的,无需评论 :) 这个技巧在我的四个网络上完美运行了多年。但如果您不理解为什么这可能是不良实践,请继续学习!包括bash变量分配和充足的间距,我的解决方案需要120个字符来完成所有操作。
eval $( awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml ); echo "host: $host, username: $username, password: $password dbname: $dbname"

1
这种方法存在严重的安全问题。您不希望包含 $(rm -rf ~) 的密码执行 eval 命令(如果您将注入的引号从双引号更改为单引号,则可以使用 $(rm -rf ~)'$(rm -rf ~)' 来避免)。 - Charles Duffy
...所以,如果你想让它更安全,你需要同时(1)从注入双引号改为单引号;和(2)用类似'"'"'的结构替换数据中的任何字面单引号。 - Charles Duffy
此外,应该使用eval "$(...)"而不仅仅是eval $(...)。如果你想知道为什么后者会导致错误的结果,请尝试运行以下命令:cmd=$'printf \'%s\\n\' \'first * line\'',然后比较eval $cmdeval "$cmd"的输出结果。如果没有引号,那么在eval开始解析之前,你的*将被替换为当前目录中的文件列表(这意味着这些文件名本身将作为代码进行评估,从而打开更多的安全问题)。 - Charles Duffy
2
不使用适当的工具解析XML或JSON是明智的建议。唯一的例外是,如果由于其大小而需要流式传输输入。 - Ihe Onwuka
很棒的解决方案。对我来说,价值包含在$3变量中(MacOS,z-shell终端)。 - ElectroBuddha

2
在研究Linux和Windows格式XML文件路径之间的翻译时,我找到了以下有趣的教程和解决方案:
  • XPath的通用信息
  • Amara - 用于XML的Python工具集合
  • 使用4Suite开发Python/XML(2部分)

1
Yuzem的方法可以通过在rdom函数和变量赋值中颠倒<>符号的顺序来改进,如下所示:
rdom () { local IFS=\> ; read -d \< E C ;}

变成:

rdom () { local IFS=\< ; read -d \> C E ;}

如果不按照这种方式解析,XML文件中的最后一个标签将永远无法被读取到。如果你打算在while循环结束时输出另一个XML文件,这可能会成为问题。

0

试试 xpe。它是专门为此目的构建的。您可以使用 python3 pip 安装它:

pip3 install xpe

你可以这样使用它:

curl example.com | xpe '//title'

上述命令返回:

示例域名


0

如果你想要 XML 属性,这个方法是可行的:

$ cat alfa.xml
<video server="asdf.com" stream="H264_400.mp4" cdn="limelight"/>

$ sed 's.[^ ]*..;s./>..' alfa.xml > alfa.sh

$ . ./alfa.sh

$ echo "$stream"
H264_400.mp4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接