如何在 Bash 中解析 XML?

理想情况下,我希望能够做的是:

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

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

我没有听说过任何纯 shell XML 解析工具。因此,您很可能需要用其他语言编写的工具。

我的 XML: : Twig Perl 模块附带了这样一个工具: xml_grep,您可以在其中编写所需的 xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt(-t选项将结果作为文本而不是 XML 提供)

可以从 shell 脚本调用的命令行工具包括:

  • 4xpath -Python 的 4号套房包的命令行包装器

  • XMLStarlet

  • 围绕 Perl 的 XPath 库的 XPath-命令行包装器

    sudo apt-get install libxml-xpath-perl
    
  • Xidel -可以处理 URL 和文件,也可以处理 JSON

我还使用 xmllint 和 xsltproc 以及一些 XSL 转换脚本,从命令行或 shell 脚本中进行 XML 处理。

查看 http://www.ofb.net/~egnor/xml2/中的 XML2,它将 XML 转换为面向行的格式。

只需使用 bash 即可轻松实现这一点。 你只需要添加这个函数:

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

现在您可以像 read 一样使用 rdom,但是对于 html 文档。 当调用 rdom 时,会将元素分配给变量 E,将内容分配给变量 C。

例如,做你想做的事情:

while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt

在研究了 XML 文件中文件路径的 Linux 和 Windows 格式之间的转换之后,我发现了一些有趣的教程和解决方案:

这实际上只是一个解释 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。因此,采取以下措施:

<tag>value</tag>

read_dom的第一个调用得到一个空字符串(因为’<’是第一个字符)。由于没有’>’字符,IFS 将其分割为 just”。Read 然后为这两个变量分配一个空字符串。第二个调用获取字符串‘ tag > value’。然后被 IFS 分割成两个字段‘ tag’和‘ value’。然后读取并赋值变量,如: ENTITY=tagCONTENT=value。第三个调用获取字符串“/tag >”。它被 IFS 分割成两个字段’/tag’和”。然后读取并赋值变量,如: ENTITY=/tagCONTENT=。第四个调用将返回一个非零状态,因为我们已经到达了文件的末尾。

现在,他的 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”(循环前面的回显)。

现在给出 input.xml的以下代码(类似于在 S3上列出 bucket 所得到的代码) :

<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>&quot;0032a28286680abee71aed5d059c6a09&quot;</ETag>
<Size>1785</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>

以及以下循环:

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 => &quot;0032a28286680abee71aed5d059c6a09&quot;
/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
}

然后当你打电话给 parse_dom:

while read_dom; do
parse_dom
done

然后给出下面的示例标记:

<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>

您应该得到以下输出:

$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789

EDIT 3 另一个 使用者说他们在 FreeBSD 中遇到了问题,建议从 read 中保存退出状态,并在 read _ dom 结束时返回,比如:

read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}

我看不出有什么不行的理由

您可以使用 xpath 实用程序,它是与 PerlXML-XPath 包一起安装的。

用法:

/usr/bin/xpath [filename] query

在露天使用时安装:

sudo zypper install xmlstarlet

或者在其他平台上尝试 cnf xml

如果您想要 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

Yuzem 的方法可以通过反转 rdom函数和变量赋值中 <>符号的顺序来改进,以便:

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

变成:

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

如果不这样进行解析,就永远不会到达 XML 文件中的最后一个标记。如果您打算在 while循环结束时输出另一个 XML 文件,那么这可能会有问题。

另一个命令行工具是我的新 西德尔。与前面提到的 XPath/xmlstarlet 相反,它还支持 XPath 2和 XQuery。

标题可以理解为:

xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt

它还有一个很酷的特性,可以将多个变量导出到 bash

eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )

$title设置为标题,将 $imgcount设置为文件中图像的数量,这应该与在 bash 中直接解析图像一样灵活。

从 Chad 的回答开始,这里是解析 UML 的 COMPLETE 工作解决方案,适当处理注释,只有2个小函数(超过2个,但是可以混合使用)。我并不是说 Chad 的那个根本不起作用,但是它对于格式不良的 XML 文件有太多的问题: 所以你必须更加巧妙地处理注释和放错位置的空格/CR/TAB/等。

这个答案的目的是为那些需要解析 UML 而不需要使用 perl、 python 或任何其他复杂工具的人提供现成的、开箱即用的 bash 函数。至于我,我不能安装 cpan,也不能为我正在使用的旧的生产操作系统安装 perl 模块,而且 python 也不可用。

首先,本文中使用的 UML 单词的定义:

<!-- comment... -->
<tag attribute="value">content...</tag>

编辑: 更新的功能,处理:

  • WebSphere xml (xmi 和 xmlns 属性)
  • 必须有一个兼容的终端与256种颜色
  • 24种灰色阴影
  • 为 IBM AIX bash 3.2.16(1)添加的兼容性

函数,首先是 xml _ read _ dom,它被 xml _ read 递归调用:

xml_read_dom() {
# https://stackoverflow.com/questions/893585/how-to-parse-xml-in-bash
local ENTITY IFS=\>
if $ITSACOMMENT; then
read -d \< COMMENTS
COMMENTS="$(rtrim "${COMMENTS}")"
return 0
else
read -d \< ENTITY CONTENT
CR=$?
[ "x${ENTITY:0:1}x" == "x/x" ] && return 0
TAG_NAME=${ENTITY%%[[:space:]]*}
[ "x${TAG_NAME}x" == "x?xmlx" ] && TAG_NAME=xml
TAG_NAME=${TAG_NAME%%:*}
ATTRIBUTES=${ENTITY#*[[:space:]]}
ATTRIBUTES="${ATTRIBUTES//xmi:/}"
ATTRIBUTES="${ATTRIBUTES//xmlns:/}"
fi


# when comments sticks to !-- :
[ "x${TAG_NAME:0:3}x" == "x!--x" ] && COMMENTS="${TAG_NAME:3} ${ATTRIBUTES}" && ITSACOMMENT=true && return 0


# http://tldp.org/LDP/abs/html/string-manipulation.html
# INFO: oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# [ "x${ATTRIBUTES:(-1):1}x" == "x/x" -o "x${ATTRIBUTES:(-1):1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:(-1)}"
[ "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x/x" -o "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:${#ATTRIBUTES} -1}"
return $CR
}

第二个:

xml_read() {
# https://stackoverflow.com/questions/893585/how-to-parse-xml-in-bash
ITSACOMMENT=false
local MULTIPLE_ATTR LIGHT FORCE_PRINT XAPPLY XCOMMAND XATTRIBUTE GETCONTENT fileXml tag attributes attribute tag2print TAGPRINTED attribute2print XAPPLIED_COLOR PROSTPROCESS USAGE
local TMP LOG LOGG
LIGHT=false
FORCE_PRINT=false
XAPPLY=false
MULTIPLE_ATTR=false
XAPPLIED_COLOR=g
TAGPRINTED=false
GETCONTENT=false
PROSTPROCESS=cat
Debug=${Debug:-false}
TMP=/tmp/xml_read.$RANDOM
USAGE="${C}${FUNCNAME}${c} [-cdlp] [-x command <-a attribute>] <file.xml> [tag | \"any\"] [attributes .. | \"content\"]
${nn[2]}  -c = NOCOLOR${END}
${nn[2]}  -d = Debug${END}
${nn[2]}  -l = LIGHT (no \"attribute=\" printed)${END}
${nn[2]}  -p = FORCE PRINT (when no attributes given)${END}
${nn[2]}  -x = apply a command on an attribute and print the result instead of the former value, in green color${END}
${nn[1]}  (no attribute given will load their values into your shell; use '-p' to print them as well)${END}"


! (($#)) && echo2 "$USAGE" && return 99
(( $# < 2 )) && ERROR nbaram 2 0 && return 99
# getopts:
while getopts :cdlpx:a: _OPT 2>/dev/null
do
{
case ${_OPT} in
c) PROSTPROCESS="${DECOLORIZE}" ;;
d) local Debug=true ;;
l) LIGHT=true; XAPPLIED_COLOR=END ;;
p) FORCE_PRINT=true ;;
x) XAPPLY=true; XCOMMAND="${OPTARG}" ;;
a) XATTRIBUTE="${OPTARG}" ;;
*) _NOARGS="${_NOARGS}${_NOARGS+, }-${OPTARG}" ;;
esac
}
done
shift $((OPTIND - 1))
unset _OPT OPTARG OPTIND
[ "X${_NOARGS}" != "X" ] && ERROR param "${_NOARGS}" 0


fileXml=$1
tag=$2
(( $# > 2 )) && shift 2 && attributes=$*
(( $# > 1 )) && MULTIPLE_ATTR=true


[ -d "${fileXml}" -o ! -s "${fileXml}" ] && ERROR empty "${fileXml}" 0 && return 1
$XAPPLY && $MULTIPLE_ATTR && [ -z "${XATTRIBUTE}" ] && ERROR param "-x command " 0 && return 2
# nb attributes == 1 because $MULTIPLE_ATTR is false
[ "${attributes}" == "content" ] && GETCONTENT=true


while xml_read_dom; do
# (( CR != 0 )) && break
(( PIPESTATUS[1] != 0 )) && break


if $ITSACOMMENT; then
# oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# if [ "x${COMMENTS:(-2):2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:(-2)}" && ITSACOMMENT=false
# elif [ "x${COMMENTS:(-3):3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:(-3)}" && ITSACOMMENT=false
if [ "x${COMMENTS:${#COMMENTS} - 2:2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 2}" && ITSACOMMENT=false
elif [ "x${COMMENTS:${#COMMENTS} - 3:3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 3}" && ITSACOMMENT=false
fi
$Debug && echo2 "${N}${COMMENTS}${END}"
elif test "${TAG_NAME}"; then
if [ "x${TAG_NAME}x" == "x${tag}x" -o "x${tag}x" == "xanyx" ]; then
if $GETCONTENT; then
CONTENT="$(trim "${CONTENT}")"
test ${CONTENT} && echo "${CONTENT}"
else
# eval local $ATTRIBUTES => eval test "\"\$${attribute}\"" will be true for matching attributes
eval local $ATTRIBUTES
$Debug && (echo2 "${m}${TAG_NAME}: ${M}$ATTRIBUTES${END}"; test ${CONTENT} && echo2 "${m}CONTENT=${M}$CONTENT${END}")
if test "${attributes}"; then
if $MULTIPLE_ATTR; then
# we don't print "tag: attr=x ..." for a tag passed as argument: it's usefull only for "any" tags so then we print the matching tags found
! $LIGHT && [ "x${tag}x" == "xanyx" ] && tag2print="${g6}${TAG_NAME}: "
for attribute in ${attributes}; do
! $LIGHT && attribute2print="${g10}${attribute}${g6}=${g14}"
if eval test "\"\$${attribute}\""; then
test "${tag2print}" && ${print} "${tag2print}"
TAGPRINTED=true; unset tag2print
if [ "$XAPPLY" == "true" -a "${attribute}" == "${XATTRIBUTE}" ]; then
eval ${print} "%s%s\ " "\${attribute2print}" "\${${XAPPLIED_COLOR}}\"\$(\$XCOMMAND \$${attribute})\"\${END}" && eval unset ${attribute}
else
eval ${print} "%s%s\ " "\${attribute2print}" "\"\$${attribute}\"" && eval unset ${attribute}
fi
fi
done
# this trick prints a CR only if attributes have been printed durint the loop:
$TAGPRINTED && ${print} "\n" && TAGPRINTED=false
else
if eval test "\"\$${attributes}\""; then
if $XAPPLY; then
eval echo "\${g}\$(\$XCOMMAND \$${attributes})" && eval unset ${attributes}
else
eval echo "\$${attributes}" && eval unset ${attributes}
fi
fi
fi
else
echo eval $ATTRIBUTES >>$TMP
fi
fi
fi
fi
unset CR TAG_NAME ATTRIBUTES CONTENT COMMENTS
done < "${fileXml}" | ${PROSTPROCESS}
# http://mywiki.wooledge.org/BashFAQ/024
# INFO: I set variables in a "while loop" that's in a pipeline. Why do they disappear? workaround:
if [ -s "$TMP" ]; then
$FORCE_PRINT && ! $LIGHT && cat $TMP
# $FORCE_PRINT && $LIGHT && perl -pe 's/[[:space:]].*?=/ /g' $TMP
$FORCE_PRINT && $LIGHT && sed -r 's/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/\1 /g' $TMP
. $TMP
rm -f $TMP
fi
unset ITSACOMMENT
}

最后,修剪、修剪和 echo 2(to stderr)功能:

rtrim() {
local var=$@
var="${var%"${var##*[![:space:]]}"}"   # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local var=$@
var="${var#"${var%%[![:space:]]*}"}"   # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}"   # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "$@" 1>&2; }

色彩处理:

哦,你需要一些简洁的着色动态变量,首先要定义,然后导出:

set -a
TERM=xterm-256color
case ${UNAME} in
AIX|SunOS)
M=$(${print} '\033[1;35m')
m=$(${print} '\033[0;35m')
END=$(${print} '\033[0m')
;;
*)
m=$(tput setaf 5)
M=$(tput setaf 13)
# END=$(tput sgr0)          # issue on Linux: it can produces ^[(B instead of ^[[0m, more likely when using screenrc
END=$(${print} '\033[0m')
;;
esac
# 24 shades of grey:
for i in $(seq 0 23); do eval g$i="$(${print} \"\\033\[38\;5\;$((232 + i))m\")" ; done
# another way of having an array of 5 shades of grey:
declare -a colorNums=(238 240 243 248 254)
for num in 0 1 2 3 4; do nn[$num]=$(${print} "\033[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "\033[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'

如何装载这些东西:

您要么知道如何创建函数并通过 FPATH (ksh)加载它们,要么知道如何模拟 FPATH (bash)

如果没有,只需在命令行上复制/粘贴所有内容。

它是如何工作的:

xml_read [-cdlp] [-x command <-a attribute>] <file.xml> [tag | "any"] [attributes .. | "content"]
-c = NOCOLOR
-d = Debug
-l = LIGHT (no \"attribute=\" printed)
-p = FORCE PRINT (when no attributes given)
-x = apply a command on an attribute and print the result instead of the former value, in green color
(no attribute given will load their values into your shell as $ATTRIBUTE=value; use '-p' to print them as well)


xml_read server.xml title content     # print content between <title></title>
xml_read server.xml Connector port    # print all port values from Connector tags
xml_read server.xml any port          # print all port values from any tags

使用 Debug 模式(- d)将注释和解析后的属性打印到 stderr

这就够了。

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt

虽然有很多现成的控制台实用程序可以满足您的需要,但是在 Python 这样的通用编程语言中编写几行代码可能花费的时间更少,因为这些代码可以很容易地扩展和适应您的需要。

下面是一个 python 脚本,它使用 lxml进行解析ーー它将文件名或 URL 作为第一个参数,XPath 表达式作为第二个参数,并打印与给定表达式匹配的字符串/节点。

例子一

#!/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:module以从 pom.xml文件获取模块。如果 p前缀已经映射到您的 XML 中,那么您需要修改脚本以使用另一个前缀。


例子2

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

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>

模块 _ 提取器. 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)

虽然“永远不要在没有合适工具的情况下从 bash 解析 XML、 JSON... ...”似乎是合理的建议,但我不同意。如果这是副业,寻找合适的工具,然后学习它是非常浪费的... Awk 可以在几分钟内完成。我的程序需要处理上面提到的以及更多种类的数据。我不想测试30个工具来解析5-7-10种不同的格式,如果我能在几分钟内解决问题的话。我不关心 XML、 JSON 或任何东西!我需要一个单一的解决方案。

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

从 bash 解析 XML 的问题是我昨天遇到的。以下是我对任何 HDF 的做法。作为一个额外的好处——我将数据直接分配给 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

我不在乎这种格式叫什么。我只寻求最简单的解决办法。在这种特殊情况下,我可以从数据中看到,newline 是记录分隔符(RS)和 < > 分隔字段(FS)。在我最初的案例中,我对两个记录中的6个值进行了复杂的索引,将它们关联起来,查找数据何时存在以及字段(记录)可能存在也可能不存在。完美地解决这个问题用了四行字。因此,在使用它之前,要根据每个需求调整想法!

第二部分简单地查看一行(RS)中是否有需要的字符串,如果有,则打印出需要的字段(FS)。上面我花了大约30秒的时间来复制和改编我上一次使用这种方法的命令(长了4倍)。就是这样!以90个字符完成。

但是,我总是需要将数据整齐地放入脚本中的变量中。我首先这样测试构造:

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

在某些情况下,我使用 printf 而不是 print。当我看到一切正常时,我只是简单地为变量赋值。我知道很多人认为“ eval”是“邪恶的”,无需多言:)多年来,Trick 在我所有的四个网络上都表现得很完美。但是,如果你不明白为什么这可能是不好的做法,继续学习!包括 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"

Yq 可用于 XML 解析 (以下例子所需的版本: > = 4.30.5)。

它是一个轻量级的、可移植的命令行 YAML 处理器,也可以处理 XML。 语法类似于 JQ

输入

<root>
<myel name="Foo" />
<myel name="Bar">
<mysubel>stairway to heaven</mysubel>
</myel>
</root>

用法示例1

yq --input-format xml '.root.myel.0.+@name' $FILE

Foo

用法示例2

yq有一个很好的内置特性,使 XML 很容易被抓取

yq --input-format xml --output-format props $FILE

root.myel.0.+@name = Foo
root.myel.1.+@name = Bar
root.myel.1.mysubel = stairway to heaven

用法示例3

yq还可以将 XML 输入转换为 JSON 或 YAML

yq --input-format xml --output-format json $FILE

{
"root": {
"myel": [
{
"+@name": "Foo"
},
{
"+@name": "Bar",
"mysubel": "stairway to heaven"
}
]
}
}

yq --input-format xml $FILE(YAML是默认格式)

root:
myel:
- +@name: Foo
- +@name: Bar
mysubel: stairway to heaven

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

pip3 install xpe

你可以这样使用它:

curl example.com | xpe '//title'

上面的命令返回:

示例域