How to get XMLLINT to put --xpath results as an array
Asked Answered
P

1

7

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'
Psittacine answered 5/2, 2019 at 21:42 Comment(5)
Without having an example input document, we can't actually test this ourselves. That said, note that ww=$(...) is a string assignment, not an array assignment, and that for i in $ww is word-splitting and glob-expanding a string, not iterating over array elements. (Even if ww is an array, that operation ignores all but the first element, and operates on that first element as a string).Chita
...if ww truly were an array, iterating over it would be for i in "${ww[@]}", but to make that true you'd need to assign to it in a completely different way.Chita
Also, you don't need to use for to run a printf string once per array element -- printf '%s\n' "${ww[@]}" would do the trick (if, again, you had actually assigned to an array in the first place).Chita
Replaced code with a MCV BASH and XML file as per Charles DuffyPsittacine
Much better. I also added in actual and (what I presume to be) desired output.Chita
C
10

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
Chita answered 6/2, 2019 at 16:52 Comment(3)
Thanks so much Charles.Psittacine
BTW, if you want to use XMLStarlet, but don't have it installed everywhere you need, it's possible to have xmlstarlet sel generate an XSLT template you can run anywhere xsltproc is installed (which should be available more places than xmllint --xpath). xmlstarlet sel -C -t -m '//absolutePath' -v './text()' -n will generate an XSLT file that can be used in that way.Chita
excellent construction. I used (and referenced) it for an answer in SOes.Raindrop

© 2022 - 2024 — McMap. All rights reserved.