Use namerefs in Bash(4)?

Viewed 443

Prerequisites :

  • Bash Version = 4.2.46

I wrote the following code:

declare -A service1=(
  [id]=1
  [name]=foo
)

declare -A service2=(
  [id]=2
  [name]=bar
)

declare -n service

for service in ${!service@}; do
  echo ${service[id]} ${service[name]}
done

My expected output:

1 foo
2 bar

But I got errors when run with bash 4:

./test.sh: line 13: declare: -n: invalid option
declare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
service1 service1
service2 service2

I find declare -n is new for Bash(5):

w. The shell has nameref' variables and new -n(/+n) options to declare and unset to use them, and a test -R' option to test for them.

How can I implement the above code in bash 4, other alternatives?

3 Answers

I suggest you dereference variables like this:

for service in ${!service@}; do
  var_serviceid=$service[id]
  var_servicename=$service[name]
  echo ${!var_serviceid} ${!var_servicename}
done

If you want the for loop to look as compact as yours, you may use a dedicated function, e.g.:

foo() {
  local v=$1[$2]
  printf -- %s "${!v}"
}

for service in ${!service@}; do
  echo $(foo $service id) $(foo $service name)
done

If you're wedded to this data structure, you can get the same result by assigning the array name + subscript to a variable, then dereferencing it (${!var}). I don't have 4.2 to test, but it should work.

v2.04

k. The ksh-93 ${!prefix*} expansion, which expands to the names of all shell variables with prefix PREFIX, has been implemented.

v2.00

q. ${!var}: indirect variable expansion, equivalent to eval ${$var}.

Here it is on my system:

$ bash --version | head -n 1
GNU bash, version 5.0.17(1)-release (i586-alpine-linux-musl)
$ cat /tmp/so-bash4-namerefs
declare -A service1=(
  [id]=1
  [name]=foo
)

declare -A service2=(
  [id]=2
  [name]=bar
)

for service in ${!service@}; do
    id="$service[id]" name="$service[name]"
    echo "${!id}" "${!name}"
done
$ bash -f /tmp/so-bash4-namerefs
1 foo
2 bar

Depending on your context, maybe there's other ways to structure the data too, like putting the service number somewhere where it can be a variable:

declare -A \
services[id1]=1 services[name1]=foo
services[id2]=2 services[name2]=bar

Or

service_ids[1]=1 service_names[1]=foo
service_ids[2]=2 service_names[2]=bar

I have bash 4.4.12 and namerefs seem to work:

declare -n p
p=PATH
echo $p # prints my PATH

In your case, I would do a

for service in service{1,2}
Related