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