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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.