24

I have to work with an output of a Java tool, which returns a map data structure that looks like HashMap<String, ArrayList<String>. I have to work with BASH and i tried to declare it as an associative array, what is very similar to a map. The declaration of the associative array in bash should be in one line, i try to do this as following.

ARRAY=(["sem1"]=("first name" "second name") ["sem2"]=("third name") ["sem3]=OTHER_LITS)

But this creates the following error:

bash: syntax error near unexpected token `('

I can define this line by line, but i want to have it in one line. How can i define a assoviative array in bash in only one line?

6
  • No map/dictionary functionality in bash???? Commented Oct 17, 2016 at 14:15
  • 2
    No there are no multi-dimensional arrays in BASH Commented Oct 17, 2016 at 14:15
  • Any suggestion how to solve my problem? Commented Oct 17, 2016 at 14:16
  • 2
    You haven't explained the problem. Why do you need multi-dimensional arrays? What are you trying to do? Commented Oct 17, 2016 at 14:20
  • I'm getting an output of an Java tool, which is a Map datastructure.. and i have to work with this map in the bash environment. Commented Oct 18, 2016 at 7:23

4 Answers 4

26

BTW, associative array, dictionary or map - all come into the one abstract data type (let's call it a dictionary).

So, here is the solution for storing array as values in the dictionary of Bash (4+ version).

Note, that array in Bash is a space delimited list of strings (so no any spaces inside the element, i.e. string), so we could write a quoted list:

"firstname middlename secondname"

as a value of the s1 key in our X dictionary:

declare -A X=(
  ['s1']="firstname middlename secondname"
  ['s2']="surname nickname"
  ['s3']="other"
)

Now we can get the value of the s1 key as array:

declare -a names=(${X[s1]})

Variable names now contains array:

> echo $names
firstname

> echo ${names[1]}
middlename

> echo ${#names[@]}
3

Finally, your question part where the strings with spaces were shown:

"first name", "second name"

Let's do a trick - represent a space as a special symbol sequence (it could be just one symbol), for example, double underscores:

"first__name", "second__name"

Declare our dictionary again, but with "escaped" spaces inside array elements:

declare -A X=(
  ['s1']="first__name middle__name second__name"
  ['s2']="surname nickname"
  ['s3']="other"
)

In this case after we get the value of the s1 key as array:

declare -a names=(${X[s1]})

We need to post process our array elements to remove __ the space-replacements to the actual space symbols. To do this we simply use replace commands of Bash strings:

> echo ${names/__/ }
first name

> echo ${names[1]/__/ }
middle name

> echo ${#names[@]}
3
Sign up to request clarification or add additional context in comments.

2 Comments

This is a brilliant solution.
This has not worked for me with bash 5.0.17(1)-release. echo $names prints "firstname middlename secondname".
6

In the absence of multi-dimensional array support in BASH, you can use this word-around associative array. Each key in the associative array is string concatenation of map-index,array-list-index:

# use one line declaration
declare -A array=([sem1,0]="first name" [sem1,1]="second name" [sem2,0]="third name" [sem3,0]="foo bar")

# loop thrpugh the map array
for i in "${!array[@]}"; do echo "$i => ${array[$i]}"; done
sem2,0 => third name
sem1,0 => first name
sem1,1 => second name
sem3,0 => foo bar

3 Comments

Hehe, this is clever.
but this makes it hard to iterate over the values of a certain key no?
Yes all solutions posted here are work arounds only and all of them have caveats
1

A more ergonomic solution that doesn't force the manipulation of the keys.

# your data with spaces
array=(1 '2 with space' 3 "4 with space and ' symbol")
declare -p array

# quote it with " and store it, your data can't contain double quote
declare -A associative=([x]=x [array]=$(printf '"%s" ' "${array[@]}"))
declare -p associative

# get your data in another array
eval deserialized_array=(${associative[array]})
declare -p deserialized_array

echo ${deserialized_array[3]}

# or let bash handle everything

# note: array contain data with double quote character
array=(1 '2 with space' 3 "4 with space and ' \" symbol")

declare -A associative=([x]=x [array]=$(declare -p array))
declare -p associative

array=() # make sure data is gone

# get the data in the same array 
eval ${associative[array]}
echo ${array[3]}

3 Comments

Nice tricks. But without explanations aimed at actually educating the reader, this isn't that helpful.
@huyz, which part of the OT question and the comments explaining what the code is doing do you need to be explain more?
I wrote that question before you edited your answer and added comments. The answer is fine now
0

From some point around Bash 4.2, you could pull off this dirty trick with "name reference."

declare -A arr_of_lists;

__list_of_key=("aaa" "bbb")
arr_of_lists["key"]="__list_of_key"

...

# After this, the variable 'nameref' is the same as the list,
# whose name was stored at 'arr_of_lists[key]'.
declare -n nameref=${arr_of_lists["key"]}

echo ${nameref[@]}     # output: aaa bbb

You could do some funky stuff to make your own multi-dimensional array implementation (excluding any exception handling for clarity).

add_key() {
  local -n __add_key__arr=$1      # 1st arg: associative array of lists.
  local key=$2         # 2nd arg: key to add.

  # To make a new list, we need to attach a "salt" to our
  # new list's name to distinguish this from any previous lists.
  local _salt=$(date +%s%N)

  # Declare a new list with the "salt".
  # ("export": create a global variable inside a function)
  eval "export -a __list_in_assoc_$_salt"
  
  # Associate 'key' to this new list.
  __add_key__arr[$key]="__list_in_assoc_$_salt"
}

add_value() {
  local -n __add_value__arr=$1      # 1st arg: associative array.
  local key="$2"       # 2nd arg: search key.
  local value="$3"     # 3rd arg: value.

  local -n list=${__add_value__arr[$key]}
  list+=("$value")
}

# Add "aaa", "bbb", and "ccc" to the key "XXX".
declare -A assoc

add_key assoc "XXX"
add_value assoc "XXX" "aaa"
add_value assoc "XXX" "bbb"
add_value assoc "XXX" "ccc"

# Check 'assoc[XXX]'
declare -n XXX_list=${assoc["XXX"]}
echo "${XXX_list[@]}"

It's basically mimicking what Bash is supposed to be doing under the hood if it supported multi-dimensional arrays.

Even though I said "dirty," I still prefer this answer to others because it doesn't involve any assumption about the data itself (e.g., the data doesn't contain a specific character, like an underscore).

1 Comment

And yes, if you need a multi-dimensional array in Bash, it's a pretty good sign that you don't want to use Bash (maybe Python would be a good fit). Regardless, there is a niche necessity that we'd prefer to use Bash but still need multi-dimensional arrays (e.g., the script should frequently call a shell command).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.