Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 395
saurav Avatar asked Jan 16 '17 11:01

saurav


People also ask

Is there a map in bash?

There are two types of arrays in Bash: indexed arrays – where the values are accessible through an integer index. associative arrays – where the values are accessible through a key (this is also known as a map)

What is $_ in shell script?

$_ (dollar underscore) is another special bash parameter and used to reference the absolute file name of the shell or bash script which is being executed as specified in the argument list. This bash parameter is also used to hold the name of mail file while checking emails. $@


2 Answers

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.

like image 73
cdarke Avatar answered Oct 12 '22 08:10

cdarke


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. :)

like image 24
ghoti Avatar answered Oct 12 '22 06:10

ghoti