Content of array in bash is OK when called directly, but lost when called from function

78 views Asked by At

I am trying to use xmllint to search an xml file and store the values I need into an array. Here is what I am doing:

#!/bin/sh

function getProfilePaths {
    unset profilePaths
    unset profilePathsArr
    profilePaths=$(echo 'cat //profiles/profile/@path' | xmllint --shell file.xml | grep '=' | grep -v ">" | cut -f 2 -d "=" | tr -d \")
    profilePathsArr+=( $(echo $profilePaths))
    return 0
}

In another function I have:

function useProfilePaths {
    getProfilePaths
    for i in ${profilePathsArr[@]}; do
    echo $i
    done
    return 0
}

useProfilePaths

The behavior of the function changes whether I do the commands manually on the command line VS calling them from different function as part of a wrapper script. When I can my function from a wrapper script, the items in the array are 1, compared to when I do it from the command line, it's 2:

$ echo ${#profilePathsArr[@]}
2

The content of profilePaths looks like this when echoed:

$ echo ${profilePaths}
/Profile/Path/1 /Profile/Path/2

I am not sure what the separator is for an xmllint call.

When I call my function from my wrapper script, the content of the first iteration of the for loop looks like this:

for i in ${profilePathsArr[@]}; do
    echo $i
done

the first echo looks like:

/Profile/Path/1
/Profile/Path/2

... and the second echo is empty.

Can anyone help me debug this issue? If I could find out what is the separator used by xmllint, maybe I could parse the items correctly in the array.

FYI, I have already tried the following approach, with the same result:

profilePaths=($(echo 'cat //profiles/profile/@path' | xmllint --shell file.xml | grep '=' | grep -v ">" | cut -f 2 -d "=" | tr -d \"))
3

There are 3 answers

0
Gilles Quénot On

Instead of using the --shell switch and many pipes, you should use the proper --xpath switch.

But as far of I know, when you have multiple values, there's no simple way to split the different nodes.

So a solution is to iterate like this :

profilePaths=(
    $(
        for i in {1..100}; do
            xmllint --xpath "//profiles[$i]/profile/@path" file.xml || break
        done
    )
)

or use :

profilePaths=( $(xmlstarlet sel -t -v "//profiles/profile/@path" file.xml) )

it display output with newlines by default

0
jan On

The problem you're having is related to data encapsulation; specifically, variables defined in a function are local, so you can't access them outside that function unless you define them otherwise.

Depending on the implementation of sh you're using, you may be able get around this by using eval on your variable definition or with a modifier like global for mksh and declare -g for zsh and bash. I know that mksh's implementation definitely works.

0
Joey Cote On

Thank you for providing feedback on how I can resolve this problem. After investigating more, I was able to make this work by changing the way I was iterating the content of my 'profilePaths' variable to insert its values into the 'profilePathsArr' array:

# Retrieve the profile paths from file.xml and assign to 'profilePaths'
profilePaths=$(echo 'cat //profiles/profile/@path' | xmllint --shell file.xml | grep '=' | grep -v ">" | cut -f 2 -d "=" | tr -d \")

# Insert them into the array 'profilePathsArr'
IFS=$'\n' read -rd '' -a profilePathsArr <<<"$profilePaths"

For some reason, with all the different function calls from my master script and calls to other scripts, it seemed like the separators were lost along the way. I am unable to find the root cause, but I know that by using "\n" as the IFS and a while loop, it worked like a charm.

If anybody wishes to add more comments on this, you are more than welcome.