如何在Bash中解析XML?
如何在Bash中解析XML?
理想情况下,我希望能够做到的是:
cat xhtmlfile.xhtml | getElementViaXPath --path='/html/head/title' | sed -e 's%(^$)%%g' > titleOfXHTMLPage.txt
可以从Shell脚本调用的命令行工具包括:
-
xpath - Perl XPath库的命令行包装器
sudo apt-get install libxml-xpath-perl
-
Xidel - 还可以处理JSON以及URL和文件
我也使用xmllint和xsltproc,配合小的XSL转换脚本,从命令行或shell脚本中进行XML处理。
这仅仅是对Yuzem的回答进行解释,但是我不想对别人做那么多的编辑,而且注释不允许格式化,所以……
rdom () { local IFS=\> ; read -d \< E C ;}
让我们叫它“read_dom”而不是“rdom”,加上一些空格,并使用更长的变量:
read_dom () { local IFS=\> read -d \< ENTITY CONTENT }
好的,它定义了一个名为read_dom的函数。第一行使IFS(输入字段分隔符)在此函数中为局部变量,并将其更改为“>”。这意味着当您读取数据时,它不会自动拆分为空格、制表符或换行符,而是会拆分为“>”。下一行表示从stdin读取输入,而不是在遇到换行符时停止,而是看到“<”字符停止(使用-d作为定界符标志)。然后将读取的内容使用IFS进行拆分,并分配给变量ENTITY和CONTENT。 因此,取以下内容:
value
第一次调用read_dom
得到一个空字符串(因为“<”是第一个字符)。这通过IFS被拆分为'',因为没有“>”字符。然后读取将空字符串分配给两个变量。第二次调用得到字符串“tag>value”。这将被IFS拆分为“tag”和“value”两个字段。然后读取将变量分配如下:ENTITY=tag
以及 CONTENT=value
。第三次调用得到字符串“/tag>”。这通过IFS拆分为两个字段“/tag”和''。然后读取将变量分配如下:ENTITY=/tag
以及 CONTENT=
。第四次调用将返回非零状态,因为我们已经到文件结尾了。
现在,他的while循环将被清理如上所述:
while read_dom; do if [[ $ENTITY = "title" ]]; then echo $CONTENT exit fi done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
第一行表示,“当read_dom函数返回零状态时,执行以下操作。”第二行检查我们刚刚看到的实体是否为“title”。下一行回显标签的内容。第四行退出。如果不是title实体,则循环重复在第六行。我们将“xhtmlfile.xhtml”重定向到标准输入(对于read_dom
函数),并将标准输出重定向到“titleOfXHTMLPage.txt”(循环中的先前回显)。
现在假设有以下内容(类似于在S3上列出存储桶时得到的内容)用于input.xml
:
sth-items false item-apple-iso@2x.png 2011-07-25T22:23:04.000Z "0032a28286680abee71aed5d059c6a09" 1785 STANDARD
以及以下循环:
while read_dom; do echo "$ENTITY => $CONTENT" done < input.xml
您应该会得到:
=> ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/" => Name => sth-items /Name => IsTruncated => false /IsTruncated => Contents => Key => item-apple-iso@2x.png /Key => LastModified => 2011-07-25T22:23:04.000Z /LastModified => ETag => "0032a28286680abee71aed5d059c6a09" /ETag => Size => 1785 /Size => StorageClass => STANDARD /StorageClass => /Contents =>
因此,如果我们像Yuzem那样编写了一个while
循环:
while read_dom; do if [[ $ENTITY = "Key" ]] ; then echo $CONTENT fi done < input.xml
我们会得到S3存储桶中所有文件的列表。
编辑
如果local IFS=\>
由于某些原因无法为您工作,并且您将其设置为全局变量,则应在函数末尾将其重置如下:
read_dom () { ORIGINAL_IFS=$IFS IFS=\> read -d \< ENTITY CONTENT IFS=$ORIGINAL_IFS }
否则,您稍后在脚本中进行的任何行拆分将出现问题。
编辑2
要拆分出属性名称/值对,您可以将read_dom()
修改为以下内容:
read_dom () { local IFS=\> read -d \< ENTITY CONTENT local ret=$? TAG_NAME=${ENTITY%% *} ATTRIBUTES=${ENTITY#* } return $ret }
然后编写您的函数以解析并获取所需的数据,如下所示:
parse_dom () { if [[ $TAG_NAME = "foo" ]] ; then eval local $ATTRIBUTES echo "foo size is: $size" elif [[ $TAG_NAME = "bar" ]] ; then eval local $ATTRIBUTES echo "bar type is: $type" fi }
然后,在你调用read_dom
的同时调用parse_dom
:
while read_dom; do parse_dom done
然后,给出以下示例标记:
bars content foos content
您应该得到以下输出:
$ cat example.xml | ./bash_xml.sh bar type is: metal foo size is: 1789
编辑3另一个用户表示他们在FreeBSD中遇到了问题,并建议保存read的退出状态,并在read_dom的末尾返回它,如下所示:
read_dom () { local IFS=\> read -d \< ENTITY CONTENT local RET=$? TAG_NAME=${ENTITY%% *} ATTRIBUTES=${ENTITY#* } return $RET }
我不认为这是有问题的。