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'")
CodePudding user response:
NOTES:
- skipping discussion on why
eval
may not be the best approach - skipping discussion on an alternative approach that uses namerefs
- will focus on how OP's current code is (not) creating the desired array and a quick fix
Arrays 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
CodePudding user response:
Using declare
in a function makes the variable local, so it's probably impossible to define a new associative array in the parent scope from within a function.
For a normal array it's possible, check just the result of this:
#!/bin/bash
make_it_an_array() {
[[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*$ ]] || return 1
eval "$1=()"
}
fun1() {
local arr # arr isn't an array, so what will happen?
make_it_an_array arr
printf '"%s" in %s: %s\n' "$1" "$FUNCNAME" "$(declare -p arr 2> /dev/null)"
}
#############################################################
fun1 arr
printf '"arr" in main: %s\n' "$(declare -p arr 2> /dev/null)"
"arr" in fun1: declare -a arr='()'
"arr" in main:
And if you replace eval "$1=()"
with declare -ga "$1=()"
the result is:
"arr" in fun1: declare -a arr='()'
"arr" in main: declare -a arr='()'
which means that the local scope of the variable in the parent function is lost
Aside:
If $list
only contains the name of a variable then you can replace:
eval "declare -a ${list}=(); declare -A ${list}_A=()"
With:
declare -a "$list=()"
declare -A "${list}_A=()"