0

I want to populate an associative array with successive output from an instruction.

With attention for the quotes, I first tried this

declare -A aa
dict='[TAG1]="VALUE 1" [TAG2]="VALUE 2"'
aa="( ${dict} )"
dict='[TAG3]="VALUE 3" [TAG4]="VALUE 4"'
aa+="( ${dict} )"
declare -p aa

but the variable content is not split in keys and values:

declare -A aa=([0]="( [TAG1]=\"VALUE 1\" [TAG2]=\"VALUE 2\" )( [TAG3]=\"VALUE 3\" [TAG4]=\"VALUE 4\" )" )

While a more explicit version works as expected:

declare -A aa
aa=( [TAG1]="VALUE 1" [TAG2]="VALUE 2" )
aa+=( [TAG3]="VALUE 3" [TAG4]="VALUE 4" )
declare -p aa

So I used a workaround

dict='[TAG1]="VALUE 1" [TAG2]="VALUE 2"'
declare -A aa="( ${dict} )"
dict='[TAG3]="VALUE 3" [TAG4]="VALUE 4"'
declare -A aa+="( ${dict} )"
declare -p aa

that delivers the expected result:

declare -A aa=([TAG4]="VALUE 4" [TAG1]="VALUE 1" [TAG3]="VALUE 3" [TAG2]="VALUE 2" )

Now my ultimate goal was to update an associative array in a function, the array being an argument to the function. Passing the array by reference, it should look something like this

modaa () {
declare -Ag "$1"
local -n ref="$1"

dict='[TAG1]="VALUE 1" [TAG2]="VALUE 2"' # In reality, this is the output of an instruction, i.e. $(lsblk -P ...)

ref+="( ${dict} )"                  # NOK: Assign "$dict" to key [0]
# declare -A ref+="( ${dict} )"     # No effect
# declare -Ag ref+="( ${dict} )"    # No effect
# ref+=( [TAG1]="VALUE 1" [TAG2]="VALUE 2" )    # Always works but does not use $dict
echo "${!ref[@]}"
}

But because of the issue explained above, the simpler expression doesn't work:

declare -A aa
modaa aa
declare -p aa

declare -A aa=([0]="( [TAG1]=\"VALUE 1\" [TAG2]=\"VALUE 2\" )" )

and the workaround of using 'declare' each time to update does not work within a function.

Did I miss something or is the solution to parse $dict and assign key/value pairs one by one?

2
  • within the function, where is ${dict} comming from? is supposed to be arg #2 ($2) or does the function just assume the caller has defined ${dict}? please update the question with a few sample calls to the function to include the ${dict} values and the results following each function call (eg, output from declare -p array) Commented Apr 30, 2022 at 19:07
  • In my example function dict is global, but in my actual script it is defined within the function as the output of an instruction like $(lsblk -P -o name,path,uuid,partuuid,mountpoint | grep -e "${dexp[UUID]}" | sed -E -e 's/([[:alnum:]]+)=/\['"${dexp[ID]}"',\1\]=/g' - ) I did not think it relevant and my question was already quite long. Commented May 1, 2022 at 9:35

2 Answers 2

0

It seems we cannot use the declare -A ref="( $str )" or local -A ref="( $str )" syntax with the references. Alternatively how about expanding the string into a temporary array variable:

#!/bin/bash

modaa () {
    declare -Ag "$1"
    local -n ref="$1"
    local -A temp="( $2 )"
    local key
    for key in "${!temp[@]}"; do
        ref[$key]=${temp[$key]}
    done
}

modaa "aa" '[TAG1]="VALUE 1" [TAG2]="VALUE 2"'
declare -p aa

Output:

declare -A aa=([TAG1]="VALUE 1" [TAG2]="VALUE 2" )

Another option may be to use eval, although we should avoid using eval as much as possible:

#!/bin/bash

modaa () {
    eval "$1=( "$2" )"
}

modaa "aa" '[TAG1]="VALUE 1" [TAG2]="VALUE 2"'
declare -p aa

As you know eval has severe security risks. Please make sure the variables passed to eval are fully under control without any chance of tampering by others.

Sign up to request clarification or add additional context in comments.

1 Comment

I would definitely want to avoid eval and I would resort to creating a temporary variable and looping through the key/value pairs if I have to. It just seems odd that the issue could be solved by using a form that should work ref+=" ${dict}" and it just doesn't and I don't see why.
0

Assumptions:

  • format of the data in the $dict variable is always of the form [index]="value string" where ...
  • there are no equal signs (=) nor linefeeds embedded in "value string"

One idea using a nameref for the array:

modaa () {

# Usage:
#         modaa array_name [index1]="string1 value"
#         modaa array_name [index1]="string1 value" ... [indexN]="stringN value"

local -n ref=$1                                # array_name
shift

while IFS='=' read -r ndx val
do
    ndx="${ndx//[][]/}"                        # remove '[]' characters
    val="${val//\"/}"                          # remove double quotes

    ref+=( [${ndx}]="${val}" )                 # create array entry

done < <(sed 's/"[[:space:]]/"\n/g' <<< $@)    # break multiple [index]="string value" entries into separate lines

}

Taking the function for a test drive:

unset      aa bb
declare -A aa bb

dict='[TAG1]="VALUE 1"'
modaa aa "${dict}"

dict='[TAG2]="VALUE 2"'
modaa bb "${dict}"

dict='[TAG3]="VALUE 3"'
modaa aa "${dict}"

dict='[TAG5]="VALUE 5" [TAG6]="VALUE 6"'
modaa bb "${dict}"

Results:

$ declare -p aa bb
declare -A aa=([TAG1]="VALUE 1" [TAG3]="VALUE 3" )
declare -A bb=([TAG2]="VALUE 2" [TAG5]="VALUE 5" [TAG6]="VALUE 6" )

1 Comment

Parsing $dict and assigning key/value pairs one by one would obviously work and the proposed code seems an elegant implementation. My question really was about why aa="( ${dict} )" doesn't do what one would expect to do and if there is really no way to fix it, rather than working around it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.