How to create a map of key:array in shell? - arrays

I want to create map in shell. Where each value is an array. So the map is key:array pair. For example it can be like this :
"Key1" : a1 a2 a3 a4
"key2" : b1 b2 b3
"key3" : c1
basically my code looks like this
listService(){
serviceType=$1
servicesList=($(getServices $serviceType))
}
listService serviceTypeA
listService serviceTypeB
listService serviceTypeC
here getServices is a function which returns an array of services based on the argument passed as $serviceType. So every time i call the listService function my serviceList gets overridden by new service list. But I want to keep all the services from different service type in form of a map like this :
"serviceA" : a1 a2 a3 a4
"serviceB" : b1 b2 b3
"serviceC" : c1
After that I want to access each array based on the key. How to achieve this.
Thanks in advance for your help.
Edit : I tried the answer provided by #cdarke . Here is my code now :
#!/bin/bash
declare -A arrayMap
getValues(){
key=$1
case $key in
AAA )
arr=( AA AAA AAAA )
;;
BBB )
arr=( BB BB BBBB )
;;
CCC )
arr=()
;;
esac
echo "${arr[#]}"
}
fillArrayMap(){
param=$1
values=( $(getValues $param) )
printf "\nIn $param\n"
echo -e "\nArray values is: ${values[#]}\n"
printf "\nLength of the array values is : ${#values[#]}\n"
arrayMap["$param"]=$values #THIS IS THE KEY LINE
valuesList=${arrayMap[$param]}
echo -e "\nArray valuesList is: ${valuesList[#]}\n"
printf "\nLength of the array valuesList is : ${#valuesList[#]}\n"
}
fillArrayMap AAA
fillArrayMap BBB
fillArrayMap CCC
Now from output I can see valuesList is getting only the first element of the values array. But I want valuesList to contain all the elements returned by the method getValues. i.e
valuesList= ${arrayMap[$param]}
now valuesList should contain all the elements, instead now it contains only 1 element. How to fix that ?
Note: My goal is to access each individual element like AAA or AA, I don't need it as a whole as a string like AA AAA AAAA

Bash does not support multi-dimensional arrays, but I don't think you need one. You can store a string in the form of a list in an array element, which will give you what you ask for.
# My made-up version of getServices
getServices() {
nm="$1"
last=${nm##*Type}
retn=(${last}1 ${last}2 ${last}3 ${last}4)
echo "${retn[#]}"
}
declare -A serviceList
listService(){
serviceType="$1"
# Here I use the key to make an assignment, which adds to the hash
serviceList["$serviceType"]=$(getServices $serviceType)
}
listService serviceTypeA
listService serviceTypeB
listService serviceTypeC
for key in ${!serviceList[#]}
do
echo "\"$key\": ${serviceList[$key]}"
done
Gives:
"serviceTypeC": C1 C2 C3 C4
"serviceTypeB": B1 B2 B3 B4
"serviceTypeA": A1 A2 A3 A4
EDIT for new question:
alter:
arrayMap["$param"]=$values # THIS IS THE KEY LINE
valuesList=${arrayMap[$param]}
to:
arrayMap["$param"]=${values[#]}
valuesList=( ${arrayMap[$param]} )
When you refer to an array variable by just it's name ($values) you only get the first element.

As cdarke already mentioned, bash arrays are one-dimensional. Over the years, folks have come up with ways to "fake" multi-dimensional arrays.
Two methods I've used are to maintain an array of array descriptions, or an array of pointers to other arrays. I'll answer with the former; the latter should be obvious if you want to explore on your own.
Here's a minimal example of array content getting used to populate variables:
#!/usr/bin/env bash
declare -A a=(
[b]='([0]="one" [1]="two")'
[c]='([0]="three" [1]="four")'
)
declare -p a
for key in ${!a[#]}; do
declare -a $key="${a[$key]}"
declare -p $key
done
Produces:
declare -A a=([b]="([0]=\"one\" [1]=\"two\")" [c]="([0]=\"three\" [1]=\"four\")" )
declare -a b=([0]="one" [1]="two")
declare -a c=([0]="three" [1]="four")
The critical bit here is that you're using declare to refer to the value of $key, since you can't just say $var="value" in bash.
Of course, you don't need to name your variables for the value of $key if you don't want to. Storing values in, say $value, would free you up to use special characters in $key.
An even simpler alternative, if it doesn't offend your sensibilities or restrict your key names too much, is to store the entire output of a declare -p command in the value of the array, and then eval it when you need it. For example:
declare -A a=(
[b]='declare -a b=([0]="one" [1]="two")'
[c]='declare -a c=([0]="three" [1]="four")'
)
for key in ${!a[#]}; do
eval "${a[$key]}"
done
Some people don't like eval. :-) It remains, however in your toolbox.
In your case, it's a little hard to advise because you haven't provided a full MCVE, but here's my contrived example.
#!/usr/bin/env bash
# contrived getServices function, since you didn't provide one
getServices() {
local -a value=()
local last="${1:$((${#1}-1)):1}" # last character of $1
for n in $( seq 1 $(( $RANDOM / 8192 + 1 )) ); do
value+=(${last}${n})
done
declare -p value # output of this function is actual bash code.
}
# populate the array
listService() {
servicesList[$1]=$( getServices $1 )
}
# Initialize this as empty to make `eval` safer
declare -A servicesList=()
# These services seem interesting.
listService serviceA
listService serviceB
listService serviceC
# Note that we're stepping through KEYS here, not values.
for row in "${!servicesList[#]}"; do
printf '"%s": ' "$row"
eval "${servicesList[$row]}" # Someone is bound to complain about this.
for column in "${!value[#]}"; do
# Add whatever $row and $column specific code you like here.
printf '%s ' "${value[$column]}"
done
printf "\n"
done
My output:
$ bash 2dimarrayexample
"serviceC": C1
"serviceB": B1 B2 B3 B4
"serviceA": A1 A2
Of course, your output may differ, since getServices produces random output. :)

Related

How to re-sort an indexed array of associative arrays, based on the values in the associative arrays?

In my shell code, I have an indexed array, containing the names of associative arrays:
declare -A assoc1=([name]=aaa [age]=20)
declare -A assoc2=([name]=bbb [age]=40)
declare -A assoc3=([name]=ccc [age]=25)
indexed_array=(assoc1 assoc2 assoc3)
So, using the above, ${indexed_array[#]} equals assoc1 assoc2 assoc3.
I want a sort_array function that can re-sort the values in indexed_array, so that the associative array with the highest age (assoc2) is listed first or last, like so:
new_indexed_array=( $(echo ${indexed_array[#]} | sort_by 'age' 'desc') )
After that I should get re-ordered contents in the new array:
declare -p new_indexed_array
# gives "assoc2 assoc3 assoc1"
I have some boilerplate code to get into the array values, but wasn't able to get any further in sorting the arrays..
function sort_by {
# for each hash in the given array
get_stdin # (custom func, sets $STDIN)
for hash in ${STDIN[#]}
do
# get the hash keys
hash_keys="$(eval "echo \${!$hash[#]}")"
# for each key
for hashkey in $hash_keys
do
# reset
return_the_array=false
# if $hashkey matches the key given
if [ "$hashkey" = "$1" ];then
# check the value of this one if highest/lowest
# (compared to previous ones)
# and then return if yes/mo (asc/desc)
fi
# if $return_the_array = true, then we found the right key and
# it's higher/lower
if [ "$return_the_array" = true ];then
# do stuff
fi
done
done
}
If you have Bash 4.3 or newer, you can use namerefs for this, as follows:
sort_by() {
local arr field sort_params elem
declare -n arr=$1
field=$2
# Build array with sort parameters
[[ $3 == 'desc' ]] && sort_params+=('-r')
[[ $field == 'age' ]] && sort_params+=('-n')
# Schwartzian transform
for elem in "${arr[#]}"; do
declare -n ref=$elem
printf '%s\t%s\n' "${ref["$field"]}" "$elem"
done | sort "${sort_params[#]}" | cut -f2
}
declare -A assoc1=([name]=aaa [age]=20)
declare -A assoc2=([name]=bbb [age]=40)
declare -A assoc3=([name]=ccc [age]=25)
indexed_array=(assoc1 assoc2 assoc3)
readarray -t byage < <(sort_by indexed_array age desc)
declare -p byage
readarray -t byname < <(sort_by indexed_array name asc)
declare -p byname
The calling syntax is a bit different:
sort_by ARRAYNAME FIELD SORTORDER
and the output is one element per line, so to read it back into an array, we have to use something like readarray (see examples at the end).
First, we use a nameref to assign the arrayname to arr:
declare -n arr=$1
arr now behaves as if it were the actual array.
Then, we build an array with the parameters for sort: if the third parameter is desc, we use -r, and if the field is age, we use -n. This could be made a bit smarter and check if the field contains numeric values or not, and set -n accordingly.
We then iterate over the elements of arr, the elements of which are the names of the associative arrays. In the loop, we assign the names to ref:
declare -n ref=$elem
ref now behaves like the actual associative array.
To sort, we use a Schwartzian transform (decorate – sort – undecorate) by printing lines with the chosen field name and then the array name; for example, for age, we'd get
20 assoc1
40 assoc2
25 assoc3
This is piped to sort with the proper parameters, and with cut -f2 we remove the sort field again.
The output for the examples looks like this:
declare -a byage=([0]="assoc2" [1]="assoc3" [2]="assoc1")
declare -a byname=([0]="assoc1" [1]="assoc2" [2]="assoc3")
Notice that declare -n declares local parameters in the function, so they don't pollute the global namespace.
Just for reference, I am using a modified version of the accepted answer:
function sort_by {
local field sort_params elem
field=$1
# Build array with sort parameters
[[ $2 == 'desc' ]] && sort_params+=('-r')
[[ $field == 'age' ]] && sort_params+=('-n')
# Schwartzian transform,
# get piped array contents from $(cat)
while read -r elem; do
declare -n ref=$elem
printf '%s\t%s\n' "${ref["$field"]}" "$elem"
done | sort "${sort_params[#]}" | cut -f2 | tr '\n' ' '
}
The difference between this function and the accepted answer, is that this function above expects to receive the contents of the indexed array as piped input, rather than taking the array name as the first parameter..
Therefore, my function can be called like so:
echo ${someIndexedArray[#]} | sort_by 'age' 'desc'
(And this func outputs everything on the same line, as opposed to newlines)
The benefit of this (for me) is that it now works in the CMS I am using, and also ,the sort_by function does not need to know the name of the array - which would not be passed by the CMS.

Ordered associative arrays in bash

I can do the following in bash:
declare -A data
data[A]="aaa"
data[C]="ccc"
data[B]="bbb"
for i in "${!data[#]}" ; do
printf "%-20s ---> %s\n" "$i" "${data[$i]}"
done
Which outputs:
A ---> aaa
B ---> bbb
C ---> ccc
That is, the associative array is reordered (I assume using lexicographic ordering on the keys, but I am not sure), and loses the original order in which I created the data. I wanted instead:
A ---> aaa
C ---> ccc
B ---> bbb
In python I would use an OrderedDict instead of a plain dict for that. Is there a similar concept in bash?
As already answered, associative arrays are not ordered. If you want ordering in that array, use below work-around.
declare -A data
data_indices=()
data[A]="aaa"; data_indices+=(A)
data[C]="ccc"; data_indices+=(C)
data[B]="bbb"; data_indices+=(B)
for i in "${data_indices[#]}" ; do
printf "%-20s ---> %s\n" "$i" "${data[$i]}"
done
There is no defined ordering of associative arrays in Bash. The ordering of output from your script will therefore be unpredictable. If you want to store more information in your array, I would suggest to create another parallel associative array that would use same keys.

How to copy an array in Bash?

I have an array of applications, initialized like this:
depends=$(cat ~/Depends.txt)
When I try to parse the list and copy it to a new array using,
for i in "${depends[#]}"; do
if [ $i #isn't installed ]; then
newDepends+=("$i")
fi
done
What happens is that only the first element of depends winds up on newDepends.
for i in "${newDepends[#]}"; do
echo $i
done
^^ This would output just one thing. So I'm trying to figure out why my for loop is is only moving the first element. The whole list is originally on depends, so it's not that, but I'm all out of ideas.
a=(foo bar "foo 1" "bar two") #create an array
b=("${a[#]}") #copy the array in another one
for value in "${b[#]}" ; do #print the new array
echo "$value"
done
The simplest way to copy a non-associative array in bash is to:
arrayClone=("${oldArray[#]}")
or to add elements to a preexistent array:
someArray+=("${oldArray[#]}")
Newlines/spaces/IFS in the elements will be preserved.
For copying associative arrays, Isaac's solutions work great.
The solutions given in the other answers won't work for associative arrays, or for arrays with non-contiguous indices. Here are is a more general solution:
declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp=$(declare -p arr)
eval "${temp/arr=/newarr=}"
diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output
And another:
declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
declare -A newarr
for idx in "${!arr[#]}"; do
newarr[$idx]=${arr[$idx]}
done
diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output
Try this: arrayClone=("${oldArray[#]}")
This works easily.
array_copy() {
set -- "$(declare -p $1)" "$2"
eval "$2=${1#*=}"
}
# Usage examples:
these=(apple banana catalog dormant eagle fruit goose hat icicle)
array_copy these those
declare -p those
declare -A src dest
source=(["It's a 15\" spike"]="and it's 1\" thick" [foo]=bar [baz]=qux)
array_copy src dest
declare -p dest
Note: when copying associative arrays, the destination must already exist as an associative array. If not, array_copy() will create it as a standard array and try to interpret the key names from the associative source as arithmetic variable names, with ugly results.
Isaac Schwabacher's solution is more robust in this regard, but it can't be tidily wrapped up in a function because its eval step evaluates an entire declare statement and bash treats those as equivalent to local when they're inside a function. This could be worked around by wedging the -g option into the evaluated declare but that might give the destination array more scope than it's supposed to have. Better, I think, to have array_copy() perform only the actual copy into an explicitly scoped destination.
You can copy an array by inserting the elements of the first array into the copy by specifying the index:
#!/bin/bash
array=( One Two Three Go! );
array_copy( );
let j=0;
for (( i=0; i<${#array[#]}; i++)
do
if [[ $i -ne 1 ]]; then # change the test here to your 'isn't installed' test
array_copy[$j]="${array[$i]}
let i+=1;
fi
done
for k in "${array_copy[#]}"; do
echo $k
done
The output of this would be:
One
Three
Go!
A useful document on bash arrays is on TLDP.
Problem is to copy array in function to be visible in parent code. This solution works for indexed arrays and if before copying are predefined as declare -A ARRAY, works also for associative arrays.
function array_copy
# $1 original array name
# $2 new array name with the same content
{
local INDEX
eval "
for INDEX in \"\${!$1[#]}\"
do
$2[\"\$INDEX\"]=\"\${$1[\$INDEX]}\"
done
"
}
Starting with Bash 4.3, you can do this
$ alpha=(bravo charlie 'delta 3' '' foxtrot)
$ declare -n golf=alpha
$ echo "${golf[2]}"
delta 3
Managed to copy an array into another.
firstArray=()
secondArray=()
firstArray+=("Element1")
firstArray+=("Element2")
secondArray+=("${firstArray[#]}")
for element in "${secondArray[#]}"; do
echo "${element}"
done
I've found that this works for me (mostly :)) ...
eval $(declare -p base | sed "s,base,target,")
extending the sed command to edit any switches as necessary e.g. if the new structure has to be writeable, to edit out read-only (-r).
I've discovered what was wrong.. My if isn't installed test is two for loops that remove excess characters from file names, and spits them out if they exist on a certain web server. What it wasn't doing was removing a trailing hyphen. So, when it tested it online for availability, they were parsed out. Because "file" exists, but "file-" doesn't.

How to assign an associative array to another variable in zsh?

In zsh, is there a way to assign an associative array to another variable? I would like to to something like this:
typeset -A orig
orig=(key1 val1 key2 val2)
typeset -A other
other=$orig
print '$orig: '$orig
print '$other: '$other
print '$orig[key1]: '$orig[key1]
print '$other[key1]: '$other[key1]
This will print:
$orig: val1 val2
$other: val1 val2
$orig[key1]: val1
$other[key1]:
I want to be able to use $other[key1] and get val1.
I know I can iterate over the keys and copy it item by item, but I really want to avoid this. Also, eval is evil :)
I have tried other=($orig) and other variations, but this will get my values from orig and create as associative array like this
other=(val1 val2)
So other[key1] returns nothing and other[val1] returns val2, which is not what I want.
If I understand correctly, what is going on in every attempt of mine is that $other is getting an array of the values of $orig, without the keys. How can I make it receive both keys and values and have the correct association between them?
I'm not worried about null values, if that would even be a problem,
because I am sure $orig will be well behaved.
Thanks!
You have to delve into the wonderful world of parameter expansion flags :) The k and v flags can be used together to force an associative array to expand to both its keys and values.
$ typeset -A orig
$ orig=(key1 val1 key2 val2)
$ print ${(kv)orig}
key1 val1 key2 val2
Then you can use the set command to populate your copy with the alternating key/values produced by that expansion.
$ typeset -A other
$ set -A other ${(kv)orig}
$ print $other[key1]
val1
These and other flags are documented in man zshexpn under "Parameter Expansion Flags", which is one of my favorite zsh features.
zsh: bad set of key/value pairs for associative array
Perfect world without escaping:
typeset -A old new
old=(k1 v1 k2 v2 k3 v3)
typeset old # old=( k1 v1 k2 v2 k3 v3 )
...doesn't exist and your arrays usually include empty values:
old[k2]=
typeset old # old=( k1 v1 k2 '' k3 v3 )
...therefore you need to use " (quoting), # (escaping) and f (field separation):
typeset new # new=( )
new=("${(#fkv)old}")
typeset new # new=( k1 v1 k2 '' k3 v3 )
See man zshexpn for more on parameter expansion flags.

How to pass an associative array as argument to a function in Bash?

How do you pass an associative array as an argument to a function? Is this possible in Bash?
The code below is not working as expected:
function iterateArray
{
local ADATA="${#}" # associative array
for key in "${!ADATA[#]}"
do
echo "key - ${key}"
echo "value: ${ADATA[$key]}"
done
}
Passing associative arrays to a function like normal arrays does not work:
iterateArray "$A_DATA"
or
iterateArray "$A_DATA[#]"
If you're using Bash 4.3 or newer, the cleanest way is to pass the associative array by name reference and then access it inside your function using a name reference with local -n. For example:
function foo {
local -n data_ref=$1
echo ${data_ref[a]} ${data_ref[b]}
}
declare -A data
data[a]="Fred Flintstone"
data[b]="Barney Rubble"
foo data
You don't have to use the _ref suffix; that's just what I picked here. You can call the reference anything you want so long as it's different from the original variable name (otherwise youll get a "circular name reference" error).
I had exactly the same problem last week and thought about it for quite a while.
It seems, that associative arrays can't be serialized or copied. There's a good Bash FAQ entry to associative arrays which explains them in detail. The last section gave me the following idea which works for me:
function print_array {
# eval string into a new associative array
eval "declare -A func_assoc_array="${1#*=}
# proof that array was successfully created
declare -p func_assoc_array
}
# declare an associative array
declare -A assoc_array=(["key1"]="value1" ["key2"]="value2")
# show associative array definition
declare -p assoc_array
# pass associative array in string form to function
print_array "$(declare -p assoc_array)"
Based on
Florian Feldhaus's solution:
# Bash 4+ only
function printAssocArray # ( assocArrayName )
{
var=$(declare -p "$1")
eval "declare -A _arr="${var#*=}
for k in "${!_arr[#]}"; do
echo "$k: ${_arr[$k]}"
done
}
declare -A conf
conf[pou]=789
conf[mail]="ab\npo"
conf[doo]=456
printAssocArray "conf"
The output will be:
doo: 456
pou: 789
mail: ab\npo
Update, to fully answer the question, here is an small section from my library:
Iterating an associative array by reference
shopt -s expand_aliases
alias array.getbyref='e="$( declare -p ${1} )"; eval "declare -A E=${e#*=}"'
alias array.foreach='array.keys ${1}; for key in "${KEYS[#]}"'
function array.print {
array.getbyref
array.foreach
do
echo "$key: ${E[$key]}"
done
}
function array.keys {
array.getbyref
KEYS=(${!E[#]})
}
# Example usage:
declare -A A=([one]=1 [two]=2 [three]=3)
array.print A
This we a devlopment of my earlier work, which I will leave below.
#ffeldhaus - nice response, I took it and ran with it:
t()
{
e="$( declare -p $1 )"
eval "declare -A E=${e#*=}"
declare -p E
}
declare -A A='([a]="1" [b]="2" [c]="3" )'
echo -n original declaration:; declare -p A
echo -n running function tst:
t A
# Output:
# original declaration:declare -A A='([a]="1" [b]="2" [c]="3" )'
# running function tst:declare -A E='([a]="1" [b]="2" [c]="3" )'
You can only pass associative arrays by name.
It's better (more efficient) to pass regular arrays by name also.
Here is a solution I came up with today using eval echo ... to do the indirection:
print_assoc_array() {
local arr_keys="\${!$1[#]}" # \$ means we only substitute the $1
local arr_val="\${$1[\"\$k\"]}"
for k in $(eval echo $arr_keys); do #use eval echo to do the next substitution
printf "%s: %s\n" "$k" "$(eval echo $arr_val)"
done
}
declare -A my_arr
my_arr[abc]="123"
my_arr[def]="456"
print_assoc_array my_arr
Outputs on bash 4.3:
def: 456
abc: 123
yo:
#!/bin/bash
declare -A dict
dict=(
[ke]="va"
[ys]="lu"
[ye]="es"
)
fun() {
for i in $#; do
echo $i
done
}
fun ${dict[#]} # || ${dict[key]} || ${!dict[#] || ${dict[$1]}
eZ
Here's another way: you can manually serialize the associative array as you pass it to a function, then deserialize it back into a new associative array inside the function:
1. Manual passing (via serialization/deserialization) of the associative array
Here's a full, runnable example from my eRCaGuy_hello_world repo:
array_pass_as_bash_parameter_2_associative.sh:
# Print an associative array using manual serialization/deserialization
# Usage:
# # General form:
# print_associative_array array_length array_keys array_values
#
# # Example:
# # length indices (keys) values
# print_associative_array "${#array1[#]}" "${!array1[#]}" "${array1[#]}"
print_associative_array() {
i=1
# read 1st argument, the array length
array_len="${#:$i:1}"
((i++))
# read all key:value pairs into a new associative array
declare -A array
for (( i_key="$i"; i_key<$(($i + "$array_len")); i_key++ )); do
i_value=$(($i_key + $array_len))
key="${#:$i_key:1}"
value="${#:$i_value:1}"
array["$key"]="$value"
done
# print the array by iterating through all of the keys now
for key in "${!array[#]}"; do
value="${array["$key"]}"
echo " $key: $value"
done
}
# Let's create and load up an associative array and print it
declare -A array1
array1["a"]="cat"
array1["b"]="dog"
array1["c"]="mouse"
# length indices (keys) values
print_associative_array "${#array1[#]}" "${!array1[#]}" "${array1[#]}"
Sample output:
a: cat
b: dog
c: mouse
Explanation:
For a given function named print_associative_array, here is the general form:
# general form
print_associative_array array_length array_keys array_values
For an array named array1, here is how to obtain the array length, indices (keys), and values:
array length: "${#array1[#]}"
all of the array indices (keys in this case, since it's an associative array): "${!array1[#]}"
all of the array values: "${array1[#]}"
So, an example call to print_associative_array would look like this:
# example call
# length indices (keys) values
print_associative_array "${#array1[#]}" "${!array1[#]}" "${array1[#]}"
Putting the length of the array first is essential, as it allows us to parse the incoming serialized array as it arrives into the print_associative_array function inside the magic # array of all incoming arguments.
To parse the # array, we'll use array slicing, which is described as follows (this snippet is copy-pasted from my answer here):
# array slicing basic format 1: grab a certain length starting at a certain
# index
echo "${#:2:5}"
# │ │
# │ └────> slice length
# └──────> slice starting index (zero-based)
2. [Better technique than above!] Pass the array by reference
...as #Todd Lehman explains in his answer here
# Print an associative array by passing the array by reference
# Usage:
# # General form:
# print_associative_array2 array
# # Example
# print_associative_array2 array1
print_associative_array2() {
# declare a local **reference variable** (hence `-n`) named `array_reference`
# which is a reference to the value stored in the first parameter
# passed in
local -n array_reference="$1"
# print the array by iterating through all of the keys now
for key in "${!array_reference[#]}"; do
value="${array_reference["$key"]}"
echo " $key: $value"
done
}
echo 'print_associative_array2 array1'
print_associative_array2 array1
echo ""
echo "OR (same thing--quotes don't matter in this case):"
echo 'print_associative_array2 "array1"'
print_associative_array2 "array1"
Sample output:
print_associative_array2 array1
a: cat
b: dog
c: mouse
OR (same thing--quotes don't matter in this case):
print_associative_array2 "array1"
a: cat
b: dog
c: mouse
See also:
[my answer] a more-extensive demo of me serializing/deserializing a regular "indexed" bash array in order to pass one or more of them as parameters to a function: Passing arrays as parameters in bash
[my answer] a demo of me passing a regular "indexed" bash array by reference: Passing arrays as parameters in bash
[my answer] array slicing: Unix & Linux: Bash: slice of positional parameters
[my question] Why do the man bash pages state the declare and local -n attribute "cannot be applied to array variables", and yet it can?
Excellent. The simple solution described by #Todd Lehman, solved my associative array passing problem. I had to pass 3 parameters, an integer, an associative array and an indexed array to a function.
A while ago I'd read that since arrays in bash are not first-class entities, arrays could not be passed as arguments to functions. Clearly that's not the whole truth after all. I've just implemented a solution where a function handles those parameters, something like this...
function serve_quiz_question() {
local index="$1"; shift
local -n answers_ref=$1; shift
local questions=( "$#" )
current_question="${questions[$index]}"
echo "current_question: $current_question"
#...
current_answer="${answers_ref[$current_question]}"
echo "current_answer: $current_answer"
}
declare -A answers
answers[braveheart]="scotland"
answers[mr robot]="new york"
answers[tron]="vancouver"
answers[devs]="california"
# integers would actually be assigned to index \
# by iterating over a random sequence, not shown here.
index=2
declare -a questions=( "braveheart" "devs" "mr robot" "tron" )
serve_quiz_question "$index" answers "${questions[#]}"
As the local variables get assigned, I had to shift the positional parameters away, in order to end with the ( "$#" ) assigning what's left to the indexed questions array.
The indexed array is needed so that we can reliably iterate over all the questions, either in random or ordered sequence. Associative arrays are not ordered data structures, so are not meant for any sort of predictable iteration.
Output:
current_question: mr robot
current_answer: new york
From the best Bash guide ever:
declare -A fullNames
fullNames=( ["lhunath"]="Maarten Billemont" ["greycat"]="Greg Wooledge" )
for user in "${!fullNames[#]}"
do
echo "User: $user, full name: ${fullNames[$user]}."
done
I think the issue in your case is that $# is not an associative array: "#: Expands to all the words of all the positional parameters. If double quoted, it expands to a list of all the positional parameters as individual words."

Resources