How do I parse certain tags from an XML file and store them in an array using Bash?

Viewed 86

I am trying to parse an XML file and keep track of certain elements to store them in an array.

An example XML file trouble.xml looks something like this:

<trouble>
<problem>
<factor name = abc/>
</problem>
<problem>
<factor name = def/>
</problem>
<trouble>

These and repeat multiple times throughout trouble.xml.

I was able to use this:

echo 'cat //factor/@name' | xmllint --shell $filename | sed -n 's: name=\"\(.*\)\":\1:p' 

Output looks like this:

abc
def

This code snippet successfully prints the "names" of the factor tag. My problem now is finding a way to somehow iterate through that list of names and save them into an array?

2 Answers

With mapfile, you don't need to loop through the results

#!/usr/bin/env bash

# Read the array from the output of processing xml
mapfile -t array < <(
  xmllint --shell <<<'cat //factor/@name' a.xml |
    sed -n 's: name=\"\(.*\)\":\1:p'
)

# Debug print the array declaration
typeset -p array

Actual output from test data:

declare -a array=([0]="abc" [1]="def")

In case your bash is too old to provide a mapfile. Here is an alternative method:

#!/usr/bin/env bash

# Read the array from the output of processing xml
IFS=$'\n' read -r -d '' -a array < <(
  xmllint --shell <<<'cat //factor/@name' a.xml |
    sed -n 's: name=\"\(.*\)\":\1:p'
)

Is this really how you normally enter an XPath query for xmllint? I never use it, but I'd be really amazed if it is.

Anyway, if you're looking for a more straight forward way without the need for cat or sed, then I can highly recommend :

$ mapfile -t myArr < <(xidel -s input.xml -e '//factor/@name')

Alternatively, xidel can do this too with --output-format=bash:

$ eval "$(xidel -s input.xml -e 'myArr:=//factor/@name' --output-format=bash)"

In both cases this will result in:

$ printf '%s\n' "${myArr[*]}" ${#myArr[*]} ${myArr[0]} ${myArr[1]}
abc def
2
abc
def
Related