How to create a map of key:array in shell?
Solution 1
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.
Solution 2
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. :)
Comments
-
saurav over 4 years
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 thelistService
function myserviceList
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 thevalues
array. But I wantvaluesList
to contain all the elements returned by the methodgetValues
. i.evaluesList= ${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