How do i iterate through an associative array in Bash where the values are arrays?

113 Views Asked by At

I wrote this code to loop through usernames and domains on my LAN. Sadly, the script prints nothing.

#!/bin/bash

construct_array_of_trgts() {
  declare -A usrs_n_dmns
  
  local -a guest_dmns
  local -a usrs=("j" "jim" "o" "root")
  local -a guest_dmns=("raspberrypi" "lenovo")
  for d in "${guest_dmns[@]}"; do 
    PS3="Select the users to include for sshing into $d. Q when done selecting."$'\n'
    local -a targt_usrs
    select u in "${usrs[@]}"; do
      if [[ "$u" ]]; then
        targt_usrs+=("$u")
     elif [[ "$REPLY" == 'q' ]]; then 
      break;
     fi
    done
    usrs_n_dmns["${d}"]="$targt_usrs"
  done
  
}
construct_array_of_trgts

for d in "${!usrs_n_dmns[@]}"; do
  targt_usrs=("${usrs_n_dmns["${d}"]}")
  echo "$usrs_n_dmns"
  for u in "${targt_usrs[@]}"; do
    echo "ssh ${u}@${d}" 
  done
done

Why doesn't this script print anything visible? Is it at all possible for an array to be a value in an associative array in Bash?

2

There are 2 best solutions below

1
Léa Gris On BEST ANSWER

As @KamilCuk explained, there are no nested or multi-dimensional arrays in Bash.

Bash arrays can only store scalar or integer values.

What is possible though, is to pass a whole array as a space-delimited concatenation of individually quoted elements that are suitable to be reused as-is in a Bash declare statement.

  1. To quote values, Bash version 4.4+ provides the @Q expansion parameter.
  2. To join array elements into a single string with each element space-delimited it needs * as the array expansion such as ${array[*]}.

Both value quoting parameter and array to string join can be combined into a single expansion such as: ${array[*]@Q}

For example:

#!/usr/bin/env bash

declare -a array=(foo bar 'Hello World')

printf '(%s)\n' "${array[*]@Q}"

Fixed your code and now it works:

#!/usr/bin/env bash

construct_array_of_trgts() {
  local -a usrs=("j" "jim" "o" "root")
  local -a guest_dmns=("raspberrypi" "lenovo")
  for d in "${guest_dmns[@]}"; do
    PS3="Select the users to include for sshing into $d. Q when done selecting."$'\n'
    local -a targt_usrs=()
    while :; do
      select u in "${usrs[@]}"; do
        if [[ "$u" ]]; then
          targt_usrs+=("$u")
        elif [[ "$REPLY" == 'q' ]]; then
          break 2
        fi
      done
    done
    # Array to scalar of space-delimited quoted values
    usrs_n_dmns["${d}"]="${targt_usrs[*]@Q}"
  done
}

declare -A usrs_n_dmns

construct_array_of_trgts

for d in "${!usrs_n_dmns[@]}"; do
  # Scalar value is expanded into array delcare
  declare -a targt_usrs="(${usrs_n_dmns[$d]})"
  for u in "${targt_usrs[@]}"; do
    echo ssh "${u}@${d}"
  done
done
0
KamilCuk On

How do i iterate through an associative array in Bash where the values are arrays?

It is impossible to do, as associative array values can't be arrays.

Why doesn't this script print anything visible?

construct_array_of_trgts() {
  declare -A usrs_n_dmns

declare within a function body is equal to local. The variable usrs_n_dmns is local within the construct_array_of_trgts function. After construct_array_of_trgts returns, usrs_n_dmns no longer exists.

If you want the variable to be global add -g to declare.

Is it at all possible for an array to be a value in an associative array in Bash?

No.