如何在Bash中parsingXML?

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

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

这实际上只是Yuzem的答案的解释,但是我不觉得应该对其他人做这么多的编辑,而且评论不允许格式化,所以…

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

我们称之为“read_dom”而不是“rdom”,把它们分开一点,并使用更长的variables:

 read_dom () { local IFS=\> read -d \< ENTITY CONTENT } 

好吧,所以它定义了一个叫做read_dom的函数。 第一行使IFS(input字段分隔符)位于此函数的本地,并将其更改为>。 这意味着当你读取数据而不是自动分割空间,制表符或换行符时,它将被分割为“>”。 下一行说从stdin读取input,而不是停在换行符处,当看到一个'<'字符(-d为deliminator标志)时停止。 然后使用IFS分割什么,然后分配给variablesENTITY和CONTENT。 所以采取以下措施:

 <tag>value</tag> 

第一次调用read_dom得到一个空string(因为'<'是第一个字符)。 这被IFS分割成“',因为没有'>'字符。 然后读取为这两个variables分配一个空string。 第二个调用获取string“tag> value”。 然后被IFS分解成两个字段'tag'和'value'。 然后读取分配variables,如: ENTITY=tagCONTENT=value 。 第三个调用获取string'/ tag>'。 这被IFS分成两个字段'/ tag'和''。 然后读取分配variables,如: ENTITY=/tagCONTENT= 。 第四个调用将返回一个非零的状态,因为我们已经到达文件的末尾。

现在他的while循环清理了一下,以匹配上面:

 while read_dom; do if [[ $ENTITY = "title" ]]; then echo $CONTENT exit fi done < xhtmlfile.xhtml > titleOfXHTMLPage.txt 

第一行只是说,“而read_domfunction返回零状态,请执行以下操作。” 第二行检查我们刚才看到的实体是否是“标题”。 下一行回显标签的内容。 四条线路退出。 如果不是标题实体,则循环在第六行重复。 我们将“xhtmlfile.xhtml”redirect到标准input(对于read_dom函数),并将标准输出redirect到“titleOfXHTMLPage.txt”(循环中较早的回声)。

现在给出以下(类似于你从S3上列出一个存储桶得到的) 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> </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=\>不适合你,你设置全局,你应该重置它的function,如:

 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 } 

然后编写你的函数来parsing和获取你想要的数据:

 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 

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

 <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 

编辑3另一个用户说,他们在FreeBSD中有问题,并build议保存退出状态从read_dom读取和返回它像:

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

我看不出有什么理由不应该这样做

你可以很容易地使用bash。 你只需要添加这个function:

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

现在你可以像使用rdom一样使用rdom,但可以使用html文档。 当被调用的rdom将元素赋值给variablesE并将其内容赋值给var C.

例如,要做你想做的事情:

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

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

  • 4xpath – 围绕Python 4Suite包的命令行包装
  • XMLStarlet
  • xpath – 围绕Perl的XPath库的命令行包装
  • Xidel – 与URL和文件一起使用。 也适用于JSON

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

您可以使用xpath实用程序。 它与Perl XML-XPath包一起安装。

用法:

 /usr/bin/xpath [filename] query 

或XMLStarlet 。 要在opensuse上安装它,请使用:

 sudo zypper install xmlstarlet 

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

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

另一个命令行工具是我的新Xidel 。 它也支持XPath 2和XQuery,与已经提到的xpath / xmlstarlet相反。

标题可以像下面这样读取:

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

而且它还有一个很酷的function来将多个variables导出到bash。 例如

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

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

我不知道任何纯粹的shellXMLparsing工具。 所以你很可能需要用其他语言编写的工具。

我的XML :: Twig Perl模块附带了这样一个工具: xml_grep ,你可能会在这里写下你想要的xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt-t选项给你结果为文本而不是xml)

这就够了

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

那么,你可以使用xpath实用程序。 我想perl的XML :: Xpath包含它。

在对XML文件中的文件path的Linux和Windows格式之间的翻译进行了一些研究之后,我发现了有趣的教程和解决scheme:

  • 有关XPath的一般信息
  • Amara – 用于XML的Pythonic工具的集合
  • 用4Suite开发Python / XML(2部分)

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

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

变为:

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

如果parsing不是这样完成的,XML文件中的最后一个标记是永远不会到达的。 如果您打算在while循环结束时输出另一个XML文件,则这可能会有问题。

从chad的答案开始,这里是COMPLETEparsingUML的工作解决scheme,只有2个小函数(超过2个bu可以混合使用)。 我没有说乍得的一个没有工作,但它有太多的XML格式错误的文件的问题:所以你必须有点棘手,处理意见和错位空间/ CR / TAB /等。

这个答案的目的是给那些需要parsingUML而无需使用perl,python或其他任何东西的复杂工具的任何人提供即用即用的bash函数。 至于我,我不能安装cpan,也不是我正在使用的旧生产操作系统的perl模块,python不可用。

首先,在这篇文章中使用的UML词的定义:

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

编辑:更新的function,处理:

  • Websphere xml(xmi和xmlns属性)
  • 必须有一个256色的兼容terminal
  • 24灰色的阴影
  • 为IBM AIX bash 3.2.16添加的兼容性(1)

函数首先是xml_read_dom,它是由xml_readrecursion调用的:

 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 } 

最后,rtrim,trim和echo2(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; } 

彩色化:

哦,你将需要一些整洁的着色dynamicvariables首先被定义,并且也被导出:

 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"' 

如何加载所有的东西:

要么你知道如何创build函数并通过FPATH(ksh)或者模拟FPATH(bash)

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

它是如何工作的:

xml_read [-cdlp] [-x命令<-a属性>] [标签| “任何”] [属性.. | “内容”]

-c = NOCOLOR

-d =debugging

-l = LIGHT(不打印\“属性= \”)

-p = FORCE PRINT(当没有给出属性时)

-x =在属性上应用命令,并以绿色打印结果而不是以前的值

(没有给出的属性会将它们的值加载到你的shell中,作为$ ATTRIBUTE = value;使用'-p'来打印它们)

 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 

使用debugging模式(-d)注释和分析的属性打印到stderr

这工作如果你想要的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 

虽然有相当多的现成的控制台实用程序可以做你想做的事情,但根据我的经验,使用通用编程语言(如Python)编写几行代码所花费的时间要less得多,您可以轻松地扩展适应您的需求。

下面是一个带有lxml模块的python脚本示例 – 它将文件的名称作为第一个参数,将XPathexpression式作为第二个参数,并打印与给定的XPathexpression式匹配的string/节点。

 #!/usr/bin/env python import sys from lxml import etree tree = etree.parse(sys.argv[1]) xpath_expression = sys.argv[2] for e in tree.xpath(xpath_expression): if isinstance(e, str): print(e) else: print(e.text or etree.tostring(e)) 

可以使用pip install lxmlpip install lxml 。 在Ubuntu上你可以使用sudo apt install python-lxml

用法

 python xmlcat.py file.xml "//mynode" 

lxml也可以接受一个URL作为input:

 python xmlcat.py http://example.com/file.xml "//mynode" 

提取enclosure节点下的url属性(即<enclosure url="http:...""..> ):

 python xmlcat.py xmlcat.py file.xml "//enclosure/@url" 

注意: lxml在速度方面performance非常好,因为它是一个用C语言编写的优化的本机模块。


2¢:不要花时间学习一些半维护实用程序的模糊语法。 生命太短暂了。 使用广泛使用的API来代替可用的资源来扩展您的编程技能。