理想情况下,我想要执行的操作是:

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


评论

unix.stackexchange.com/questions/83385/…|| superuser.com/questions/369996/…

#1 楼

这确实只是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将其拆分为''。 Read然后为两个变量分配一个空字符串。第二个调用获取字符串“ tag> value”。然后,IFS将其分为“标签”和“值”两个字段。读取然后分配变量,如:ENTITY=tagCONTENT=value。第三个调用获取字符串“ / tag>”。 IFS将其分为两个字段'/ tag'和''。 Read然后将变量分配为:ENTITY=/tagCONTENT=。第四个调用将返回非零状态,因为我们已经到达文件末尾。

现在他的while循环清理了一点,以符合上述要求:

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


第一行只是说:“当read_dom函数返回零状态时,请执行以下操作。”第二行检查我们刚刚看到的实体是否为“标题”。下一行回显标签的内容。四行出口。如果不是标题实体,则循环在第六行重复。我们将“ xhtmlfile.xhtml”重定向到标准输入(用于read_dom函数),并将标准输出重定向到“ 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=\>不适用于您,并且您对其进行了全局设置,应在功能末尾将其重置,例如:

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


然后给出以下示例标记:

<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_dom的末尾返回它,例如:

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


我看不出任何理由都不可行

评论


如果将IFS(输入字段分隔符)设置为全局,则应在最后将其重置回其原始值,我对此进行了编辑。否则,您稍后在脚本中所做的任何其他输入拆分都会被弄乱。我怀疑local对您不起作用的原因是因为您是在兼容模式下使用bash(例如您的shbang是#!/ bin / sh),或者它是bash的古老版本。

–乍得
2012年7月25日15:24



仅仅因为您可以编写自己的解析器,并不意味着您应该这样做。

–斯蒂芬·尼德斯基(Stephen Niedzielski)
13年4月23日在21:49

@chad它肯定说明了有关AWS的工作流/实现的一些信息,我正在寻找“ bash xml”的答案,也可以获取S3存储桶的内容!

–阿拉斯泰尔
2013年9月18日12:36



@Alastair参见github.com/chad3814/s3scripts,了解我们用于操纵S3对象的一组bash脚本

–乍得
13-10-11在14:27

在局部变量中分配IFS是脆弱的,没有必要。只需执行以下操作:IFS = \
–威廉·珀塞尔(William Pursell)
13年11月27日在16:47

#2 楼

您只需使用bash即可非常轻松地做到这一点。
只需添加以下功能:

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


现在,您可以像阅读一样使用rdom,但是对于html文档
当调用rdom时,会将元素分配给变量E,将内容分配给varC。例如,要做您想做的事情:

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


评论


你能详细说明一下吗?我敢打赌,这对您来说是完全清楚的..这可能是一个很好的答案-如果我能告诉您您在那儿所做的事情..您能否再细分一下,可能会产生一些示例输出?

– Alex灰色
2011年7月4日在2:14

重塑原始线条-这种单线是如此的怪异,优雅而惊人。

–小牛
2013年12月5日22:06

很棒的技巧,但是我不得不使用双引号,例如echo“ $ C”,以防止外壳扩展和正确解释端线(取决于下面的内容)

–user311174
2014年1月16日上午10:32

用grep和awk解析XML是不行的。如果XML足够简单并且您没有太多时间,这可能是一个可以接受的折衷方案,但是永远不能称其为良好的解决方案。

–peterh-恢复莫妮卡
18年2月1日在16:34

#3 楼

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


4xpath-Python 4Suite软件包的命令行包装器


XMLStarlet


xpath-Perl的XPath库的命令行包装
sudo apt-get install libxml-xpath-perl



Xidel-使用URL以及文件。我还可以使用JSON



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

评论


在哪里可以从中下载“ xpath”或“ 4xpath”?

–Opher
2011-04-15 14:47

是的,需要进行第二次投票/请求-在哪里下载这些工具,或者您是否必须手动编写包装程序?我宁愿不要浪费时间这样做,除非有必要。

–大卫
2011年11月22日,0:34

须藤apt-get install libxml-xpath-perl

–安德鲁·瓦格纳(Andrew Wagner)
2012年11月23日下午12:37

#4 楼

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

用法:

/usr/bin/xpath [filename] query


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

sudo zypper install xmlstarlet


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

评论


使用xml starlet绝对是比编写自己的序列化程序更好的选择(如其他答案所建议)。

–布鲁诺·冯·巴黎
13年2月8日在15:26

在许多系统上,预安装的xpath不适合用作脚本中的组件。参见例如stackoverflow.com/questions/15461737/…进行详细说明。

–tripleee
16年7月27日在8:47

在Ubuntu / Debian上apt-get install xmlstarlet

–rubo77
16 Dec 24'0:48

#5 楼

这样就足够了...

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


评论


在debian apt-get中安装libxml-xpath-perl。

–tres.14159
19年1月18日在13:49

#6 楼

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

评论


非常有用的工具。链接已断开,(请参阅web.archive.org/web/20160312110413/https://dan.egnor.name/xml2),但是在github上有一个有效的冻结克隆:github.com/clone/xml2

–约书亚·戈德堡
11月6日19:45

#7 楼

从乍得的答案开始,这是用于解析UML的COMPLETE工作解决方案,具有对注释的适当处理,仅具有2个小功能(超过2个bu,您可以将它们全部混合使用)。我并不是说乍得的那个根本不起作用,但是它与格式错误的XML文件存在太多问题:因此,处理注释和错位的空格/ CR / TAB / etc必须更加棘手。

这个答案的目的是为需要解析UML而无需使用Perl,python或其他任何复杂工具的任何人提供即用即用的bash函数。对我来说,我无法安装cpan,也无法为我正在使用的旧生产OS安装perl模块,并且python不可用。

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

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


编辑:更新的函数,其句柄为:


Websphere xml(xmi和xmlns属性)
必须具有256种颜色的兼容终端
为IBM AIX bash 3.2.16(1)
添加了24种灰色的兼容性
功能,首先是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=
tag=
(( $# > 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/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/ /g' $TMP
  . $TMP
  rm -f $TMP
fi
unset ITSACOMMENT
}


,最后是rtrim,trim和echo2(到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} '3[1;35m')
  m=$(${print} '3[0;35m')
  END=$(${print} '3[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} '3[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} "3[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "3[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'


如何加载所有内容:

您都知道如何创建函数并通过FPATH加载它们(ksh)或n 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


在调试模式(-d)下,注释和已解析的属性被打印到stderr

评论


我正在尝试使用上面的两个函数来生成以下内容:./read_xml.sh:第22行:(-1):子字符串表达式<0?

–khmarbaise
2014年5月5日在8:37

第22行:[“ x $ {ATTRIBUTES:(-1):1} x” ==“ x?x”] ...

–khmarbaise
2014年5月5日在8:47

抱歉,这些是bash shell函数。如果您想将它们改编为shell脚本,则一定要进行一些小的修改!更新的功能也可以处理您的错误;)

–清道夫
2015年4月8日在3:36



#8 楼

我不知道任何纯外壳XML解析工具。因此,您很可能需要用其他语言编写的工具。

我的XML :: Twig Perl模块附带了这样的工具:xml_grep,您可能会在其中编写所需的代码xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt-t选项将结果显示为文本而不是xml)

#9 楼

另一个命令行工具是我的新Xidel。与已经提到的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中解析一样灵活。 >

#10 楼

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

#11 楼

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


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


#12 楼

尽管有许多现成的控制台实用程序可以满足您的要求,但是用通用编程语言(例如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)


评论


当您想要避免安装额外的软件包或无权访问时,这真棒。在构建机器上,我可以通过apt-get或yum调用证明额外的pip安装是合理的。谢谢!

– E. Moffat
'18 -10-31在0:12

#13 楼

可以通过反转<函数中的>rdom符号的顺序以及变量分配来改进Yuzem的方法,从而:

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


变为:

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


如果不这样进行解析,则XML文件中的最后一个标记将永远不会到达。如果您打算在while循环的末尾输出另一个XML文件,则可能会出现问题。

#14 楼

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


#15 楼

虽然似乎“没有适当的工具就从不分析bash的XML,JSON ...”似乎是合理的建议,但我不同意。如果这是一项副业,那么寻找合适的工具,然后学习它就不知所措了……Awk可以在几分钟内完成。我的程序必须处理所有上述数据和更多类型的数据。地狱,如果我可以在几分钟内解决问题,我不想测试30种工具来解析5-7-10种我需要的不同格式。我不在乎XML,JSON或其他任何东西!我需要针对所有这些解决方案的单一解决方案。

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

我昨天遇到的从bash解析XML的问题。这是我对任何分层数据格式执行的操作。另外,我将数据直接分配给bash脚本中的变量。

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

在bash中解析所述XML并以90个字符的形式提取数据:

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


我通常使用可读性更高的版本,因为在现实生活中修改起来更容易,因为我经常需要进行不同的测试:

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


我不在乎该格式如何调用。我只寻求最简单的解决方案。在这种特殊情况下,我可以从数据中看到换行符是记录分隔符(RS)和<>分隔字段(FS)。在我的原始情况下,我对两个记录中的6个值进行了复杂的索引编制,将它们关联起来,查找数据何时存在以及字段(记录)是否存在。 awk花费了4行才能完美地解决该问题。因此,在使用它之前,请先将其适应每个需求!

第二部分只是看起来它在行中有想要的字符串(RS),如果是,则打印出需要的字段(FS)。上面的代码花了我大约30秒钟的时间来复制和适应上次使用这种方法的命令(时间长4倍)。就是这样!完成90个字符。

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

q4312078q

在某些情况下,我使用printf而不是print。当我看到一切看起来不错时,我只需完成为变量分配值。我知道许多人认为“ eval”是“ evil”,无需评论:) Trick多年来在我的所有四个网络上都能正常工作。但是,如果您不明白为什么这可能是不好的做法,请继续学习!包括bash变量分配和足够的间距,我的解决方案需要120个字符才能完成所有操作。

q4312078q

评论


这种方法存在严重的安全问题。您不希望使用包含$(rm -rf〜)的密码来评估该命令(如果将注入的引号从双引号更改为单引号,则可以使用$(rm -rf〜)'$(rm-射频〜)')。

–查尔斯·达菲(Charles Duffy)
11月10日20:07



...因此,如果您想确保这一点安全,则需要同时(1)从注入双引号切换为单引号;和(2)将数据中的所有文字单引号替换为类似“””的结构

–查尔斯·达菲(Charles Duffy)
11月10日20:09



同样,评估“ $(...)”,而不仅仅是评估$(...)。有关后者如何导致错误结果的示例,请尝试cmd = $'printf \'%s \\ n \'\'first * line \'',然后将eval $ cmd的输出与eval的输出进行比较“ $ cmd”-不带引号,您的*被替换为eval开始解析之前的当前目录中的文件列表(这意味着这些文件名本身将被评估为代码,从而为安全问题提供了更大的潜在空间)。

–查尔斯·达菲(Charles Duffy)
11月10日20:11