Doug R. Doug R. -4 years ago 208
Bash Question

Unable to Retrieve Key/Value Pairs From Bash Associative Array


I'm unable to retrieve the list of keys from an associative array in a bash
script. However, when I attempt to write a simplified MCVE, everything
works as expected, and I can't tell what I'm doing differently between the two.

Long Version

I'm writing a bash script which needs to read in a set of preferences from
an file and stores them in an associative array. Note that I'm using XML and
would prefer to use a Perl module to read the file, but the script will
be run on a large number of systems, most of which don't have the proper
Perl modules installed, and on almost none of which I have the necessary
access to install them. Anyway, parsing the XML file is not the issue here.

The issue is that when I run the following script against the XML file,
the settings I'm reading in from the XML file appear to be saving to the
associative array. In other words, the following code works as expected:

while read line
# Trimming code that sets key-val.
echo "${key}=${config[$key]}"

But later in the script when I attempt to retrive the config values, nothing
appears to be there. It appears that the config array is getting cleared
somehow, but I'm not clear where or how.

Here's the non-working script.

#! /bin/bash

# Configuration Array.
declare -A config


# Extract key-value pairs.
echo "Extracting and setting initial key-value pairs:"
grep "^.*<setting.*id=.*$" $CONFIG_XML |
while read line
key=$(echo "${line}" | perl -pe "s|^.*id.*?=.*?[\'\"](.*?)[\'\"].*$|\1|")
val=$(echo "${line}" | perl -pe "s|^.*value.*?=.*?[\'\"](.*?)[\'\"].*$|\1|")

# This line prints key=value pairs as expected.
echo "${key}=${config[$key]}"

# This loop does not print key=value pairs as expected.
echo -e "\n\nExtracting key-value pairs from associative array and printing:"
for key in "${!config[@]}"
echo "${key}=${config[$key]}"

echo -e "\n\nAlso doesn't work:\nkey1=${config[key1]}"


This is the contents of the config.xml file that goes along with the script:

<?xml version="1.0" encoding="UTF-8"?>
<setting id="key1" value="val1" />
<setting id="key2" value="val2" />
<setting id="key3" value="val3" />
<setting id="key4" value="val4" />
<setting id="key5" value="val5" />

Simplified MCVE

Here are the contents of my simplified MCVE, which I expected to
fail, but which works as expected:

declare -A aa
for i in {0..10}

for key in "${!aa[@]}"
# Prints key-value pairs as expected.
echo "${key}=${aa[$key]}"

Answer Source

The while loop must execute in the current shell in order for the changes to the array to take effect. One solution is to use a process substitution instead of a pipe.

while read line
    key=$(echo "${line}" | perl -pe "s|^.*id.*?=.*?[\'\"](.*?)[\'\"].*$|\1|")
    val=$(echo "${line}" | perl -pe "s|^.*value.*?=.*?[\'\"](.*?)[\'\"].*$|\1|")

    # This line prints key=value pairs as expected.
    echo "${key}=${config[$key]}"
done < <(grep "^.*<setting.*id=.*$" $CONFIG_X)

If you are using bash 4.2 or later, you can use the lastpipe option instead.

shopt -s lastpipe

grep "^.*<setting.*id=.*$" $CONFIG_XML | while read line; do
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download