When trying to define helper functions to build up associative lists, I get an error, I cannot resolve myself (BASH 4.4):
/tmp/foo.sh: line 18: 'key': syntax error: operand expected (error token is "'key'")
For BASH 4.3 I got:
/tmp/foo.sh: line 18: key: unbound variable
Here is the test case:
#!/bin/bash
set -u
# add services list
add_list()
{
local list="$1"
eval "declare -a ${list}=(); declare -A ${list}_A=()"
}
# add services to list of services
add_service()
{
local list="$1" def="$2"
local s="${def%%:*}"
eval "${list}+=('$def'); ${list}_A['$s']='$def'"
}
add_list TEST
add_service TEST 'key:value'
The reason for the two array is that I want to access elements by key, and I want to preserve the original ordering (actually ${list}+=('$s')
would be sufficient for that).
Here is the output of bash -x
:
> bash -x /tmp/foo.sh
+ set -u
+ add_list TEST
+ local list=TEST
+ eval 'declare -a TEST=(); declare -A TEST_A=()'
++ TEST=()
++ declare -a TEST
++ TEST_A=()
++ declare -A TEST_A
+ add_service TEST key:value
+ local list=TEST def=key:value
+ local s=key
+ eval 'TEST+=('\''key:value'\''); TEST_A['\''key'\'']='\''key:value'\'''
++ TEST+=('key:value')
++ TEST_A['key']=key:value
/tmp/foo.sh: line 18: 'key': syntax error: operand expected (error token is "'key'")
NOTES:
eval
may not be the best approachArrays declared in functions remain locally scoped unless the array is also declared with the g
lobal flag; consider the following:
$ mytest() { typeset -a myarray; typeset -p myarray; echo "##### mytest(): exit"; }
^^
$ unset myarray
$ mytest
declare -a myarray # array exists while inside the function
##### mytest(): exit
$ typeset -p myarray
-bash: typeset: myarray: not found # array no longer exists once outside the function
Now add the -g
flag:
$ mytest() { typeset -ag myarray; typeset -p myarray; echo "##### mytest(): exit"; }
^^^
$ unset myarray
$ mytest
declare -a myarray # array exists while inside the function
##### mytest(): exit
$ typeset -p myarray
declare -a myarray # array still exists after leaving function
Adding the -g
flag to both array declarations in OP's current function:
add_list()
{
local list="$1"
eval "declare -ag ${list}=(); declare -Ag ${list}_A=()"
# ^^^ ^^^
}
NOTE: the add_service
function definition can remain as is for now
Running OP's test:
$ unset TEST TEST_A
$ add_list TEST
$ add_service TEST 'key:value'
$ typeset -p TEST TEST_A
declare -a TEST=([0]="key:value")
declare -A TEST_A=([key]="key:value" )
As for why OP's current code generates an error ...
At the command prompt we'll emulate the add_service
operation ...
$ unset TEST TEST_A # just to make these variables are undefined before calling add_service ...
$ typeset -p TEST TEST_A # verify variables are not set
-bash: typeset: TEST: not found
-bash: typeset: TEST_A: not found
$ TEST+=('key:value') # bash recognizes this as valid array syntax and will automagically create a normal (-a) array named TEST
$ typeset -p TEST
declare -a TEST=([0]="key:value")
$ TEST_A['key']='key:value' # bash recognizes this as the correct syntax for an integer-keyed array but has problems processing the string `key` as an integer so ...
-bash: 'key': syntax error: operand expected (error token is "'key'")
$ TEST_A[key]='key:value' # again, looks like correct syntax but in this case no error ...
$ typeset -p TEST_A
declare -a TEST_A=([0]="key:value")
^^
# in this case bash considers key as a variable (ie, bash treats it as $key)
# but since $key is undefined it defaults to 0 and a normal array (-a) is
# created with index 0
$ TEST_A[xxx]='keyX:valueX'
$ typeset -p TEST_A
declare -a TEST_A=([0]="keyX:valueX") # $xxx is undefined, treated as 0, and we end up overwriting previous 0-indexed entry in array
$ key=9
$ TEST_A[key]='key:value'
$ typeset -p TEST_A
declare -a TEST_A=([0]="keyX:valueX" [9]="key:value") # $key is defined (9) so we get a new array entry with index=9