Home > Enterprise >  How to assign an associative array from a string in a function
How to assign an associative array from a string in a function

Time:05-02

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?

CodePudding user response:

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.

CodePudding user response:

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" )
  • Related