【问题标题】:How to parse XML in Bash?如何在 Bash 中解析 XML?
【发布时间】:2010-10-27 23:21:31
【问题描述】:

理想情况下,我想做的是:

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

【问题讨论】:

标签: xml bash xhtml shell xpath


【解决方案1】:

这真的只是对Yuzem's答案的解释,但我觉得不应该对别人做这么多的编辑,而且cmets不允许格式化,所以......

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

让我们称它为“read_dom”而不是“rdom”,将其留出一点空间并使用更长的变量:

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

好的,所以它定义了一个名为 read_dom 的函数。第一行使 IFS(输入字段分隔符)成为该函数的局部变量,并将其更改为 >。这意味着当您读取数据而不是自动按空格、制表符或换行符拆分时,它会按“>”拆分。下一行表示从标准输入读取输入,而不是在换行处停止,而是在看到“

<tag>value</tag>

第一次调用read_dom 得到一个空字符串(因为'' 字符。 Read 然后为这两个变量分配一个空字符串。第二次调用获取字符串'tag>value'。然后由 IFS 将其拆分为两个字段“标签”和“值”。读取然后分配变量,例如: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”。下一行回显了标签的内容。四线出口。如果它不是标题实体,则循环在第六行重复。我们将“xhtmlfile.xhtml”重定向到标准输入(用于read_dom 函数)并将标准输出重定向到“titleOfXHTMLPage.txt”(循环前面的回显)。

现在为input.xml 给出以下内容(类似于在 S3 上列出存储桶所获得的结果):

<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=\&gt; 对您不起作用并且您将其设置为全局,您应该在函数结束时将其重置,例如:

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 另一个 user 说他们在 FreeBSD 中遇到了问题,并建议将退出状态从 read 中保存并在 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 的旧版本。
  • 你可以编写自己的解析器,并不意味着你应该。
  • @chad 它确实说明了 AWS 的工作流程/实现,我正在寻找“bash xml”的答案以也 wget S3 存储桶的内容!
  • @Alastair 请参阅github.com/chad3814/s3scripts 了解我们用来操作 S3 对象的一组 bash 脚本
  • 在局部变量中分配 IFS 是脆弱的,没有必要。只需:IFS=\&lt; read ...,它只会为读取调用设置 IFS。 (请注意,我绝不赞同使用 read 解析 xml 的做法,我认为这样做充满危险,应该避免。)
【解决方案2】:

您只需使用 bash 就可以很容易地做到这一点。 你只需要添加这个函数:

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

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

例如,做你想做的事:

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

【讨论】:

  • 您能详细说明一下吗?我敢打赌,这对你来说非常清楚......这可能是一个很好的答案 - 如果我能告诉你在那里做什么......你能再分解一下,可能会产生一些示例输出吗?
  • 对原作的信任 - 这个单线非常优雅和令人惊叹。
  • 伟大的黑客,但我不得不使用双引号,如 echo "$C" 以防止外壳扩展和正确解释结束行(取决于编码)
  • 用 grep 和 awk 解析 XML 不行。如果 XML 足够简单并且您没有太多时间,这可能是一个可以接受的折衷方案,但它永远不能称为一个好的解决方案。
【解决方案3】:

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

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

  • XMLStarlet

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

    sudo apt-get install libxml-xpath-perl
    
  • Xidel - 适用于 URL 和文件。也适用于 JSON

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

【讨论】:

  • 在哪里可以下载“xpath”或“4xpath”?
  • 是的,第二次投票/请求 - 从哪里下载这些工具,或者您的意思是必须手动编写包装器?除非必要,否则我宁愿不要浪费时间这样做。
  • sudo apt-get install libxml-xpath-perl
  • xpath 太棒了!用法是简单的xpath -e 'xpath/expression/here' $filename,然后添加-q 以仅显示输出,以便您可以将其通过管道传输到其他地方或保存到​​变量。
  • 4xpath 的链接断开。
【解决方案4】:

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

用法:

/usr/bin/xpath [filename] query

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

sudo zypper install xmlstarlet

或在其他平台上尝试cnf xml

【讨论】:

  • 使用 xml starlet 绝对是比编写自己的序列化程序更好的选择(如其他答案中所建议的那样)。
  • 在许多系统上,预装的xpath 不适合用作脚本中的组件。参见例如stackoverflow.com/questions/15461737/… 详细说明。
  • 在 Ubuntu/Debian apt-get install xmlstarlet
【解决方案5】:

这就够了……

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

【讨论】:

  • 在 debian apt-get install libxml-xpath-perl .
【解决方案6】:

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

【讨论】:

【解决方案7】:

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

标题可以这样读:

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中解析一样灵活。

【讨论】:

    【解决方案8】:

    从乍得的回答开始,这里是解析 UML 的完整工作解决方案,正确处理 cmets,只有 2 个小函数(超过 2 个,您可以将它们全部混合)。我并不是说 chad 的那个根本没有用,但是它对于格式错误的 XML 文件有太多的问题:所以你必须更加棘手地处理 cmets 和错位的空格/CR/TAB/等。

    此答案的目的是为任何需要解析 UML 的人提供即用型、开箱即用的 bash 功能,而无需使用 perl、python 或其他任何复杂工具。至于我,我无法为我正在开发的旧生产操作系统安装 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递归调用的xml_read_dom:

    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(到 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
    

    使用调试模式 (-d) 将 cmets 和解析的属性打印到标准错误

    【讨论】:

    • 我正在尝试使用上述两个函数,它们会产生以下结果:./read_xml.sh: line 22: (-1): substring expression &lt; 0?
    • 第 22 行:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
    • 抱歉 khmarbaise,这些是 bash shell 函数。如果你想将它们改编为 shell 脚本,你当然必须期待一些小的改编!更新后的函数也会处理您的错误;)
    【解决方案9】:

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

    我的 XML::Twig Perl 模块带有这样一个工具:xml_grep,您可能会在其中将您想要的内容写为 xml_grep -t '/html/head/title' xhtmlfile.xhtml &gt; titleOfXHTMLPage.txt-t 选项以文本形式而不是 xml 形式提供结果)

    【讨论】:

      【解决方案10】:

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

      【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案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)
          

          【讨论】:

          • 当您想避免安装额外的软件包或无权访问时,这非常棒。在构建机器上,我可以证明额外的pip install 超过apt-getyum 调用。谢谢!
          【解决方案13】:

          Yuzem的方法可以通过颠倒rdom函数中&lt;&gt;符号的顺序和变量赋值来改进,这样:

          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 脚本中的变量。

              为了便于阅读,我将分阶段提出解决方案。从 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 个值进行了复杂的索引,将它们关联起来,查找数据何时存在以及字段(记录)可能存在也可能不存在。用了 4 行 awk 就完美解决了这个问题。所以,在使用之前根据每个需求调整想法!

              第二部分只是简单地查看一行中是否有需要的字符串(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"
              

              【讨论】:

              • 这种方法存在严重的安全问题。您不希望密码包含 $(rm -rf ~)eval 该命令(如果您将注入的引号从双引号更改为单引号,则它们可能会被 $(rm -rf ~)'$(rm -rf ~)' 击败)。
              • ...所以,如果你想让这个安全,你需要both (1)从注入双引号切换到单引号; (2) 用 '"'"' 之类的结构替换数据中的任何文字单引号
              • 另外,eval "$(...)",不仅仅是eval $(...)。有关后者如何导致错误结果的示例,请尝试cmd=$'printf \'%s\\n\' \'first * line\'',然后将eval $cmd 的输出与eval "$cmd" 的输出进行比较——如果没有引号,您的* 将被替换为文件列表在eval 开始解析之前的当前目录中(意味着那些文件名本身被评估为代码,从而为安全问题打开更多潜在空间)。
              • 永远不要在没有合适工具的情况下解析 XML 或 JSON 是合理的建议。唯一的例外是如果您需要流式传输输入,因为它的大小。
              猜你喜欢
              • 1970-01-01
              • 2016-09-15
              • 2012-05-20
              • 2017-05-18
              • 2017-09-29
              • 2014-07-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多