Search code examples
xmlbashxmllint

How to get XMLLINT to put --xpath results as an array


I would like the output of the XMLLINT to get put into an BASH array. But all I can get is a single string. The results will return many hits, none with any pattern that can help parse the returned string.

  • I have tried --format and redirect ">" to a text file.
  • I have tried xpath all instances // and just one /

mcv.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <instance>
        <absolutePath>/abc/def</absolutePath>
    </instance>
    <instance>
        <absolutePath>/abc/hij</absolutePath>
    </instance>
</root>

mcv.sh

#!/usr/bin/bash

declare -a wwArray=()

wwCount=$(xmllint --xpath 'count(//absolutePath)' "mcv.xml")

printf "wwCount: '%s' \n" ${wwCount}

i=1

while [ $i -le ${wwCount} ];
do
        wwExtracted=$(xmllint --xpath '//absolutePath['${i}']/text    ()' "mcv.xml")
        printf " - absolutePath: '%s' \n" ${wwExtracted}
        printf " - index: '%d' \n" ${i}
        let i=i+1
done 

Running this, the output is:

wwCount: '2'
 - absolutePath: '/abc/def/abc/hij'
 - index: '1'
XPath set is empty
 - absolutePath: ''
 - index: '2'

...whereas I would expect it to instead be:

wwCount: '2'
 - absolutePath: '/abc/def'
 - index: '1'
 - absolutePath: '/abc/hij'
 - index: '2'

Solution

  • The smallest change needed to make your existing code work is to add parens before the [$i], like so:

    #!/usr/bin/bash
    wwCount=$(xmllint --xpath 'count(//absolutePath)' "mcv.xml")
    for ((i=1; i<=wwCount; i++)); do
            wwExtracted=$(xmllint --xpath '(//absolutePath)['"$i"']/text()' "mcv.xml")
            printf " - absolutePath: '%s' \n" "$wwExtracted"
            printf " - index: '%d' \n" "$i"
    done 
    

    That said, this is really inefficient (running your XPath over and over). Consider switching away from xmllint to use XMLStarlet instead, which can be instructed to insert newlines between output elements, so you can tell bash to load those items directly into a real shell array:

    #!/usr/bin/bash
    readarray -t items < <(xmlstarlet sel -t -m '//absolutePath' -v './text()' -n <mcv.xml)
    printf ' - absolutePath: %s\n' "${items[@]}"
    

    Once you've got contents into an array (as created by readarray above), you can also iterate by index:

    for idx in "${!items[@]}"; do
      printf ' - absolutePath: %s\n' "${items[$idx]}"
      printf ' - index: %s\n' "$idx"
    done