Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a dynamic number of arrays with dynamic names in bash?

Tags:

arrays

bash

I am using bash version 5.1.16

I am trying to write a bash script that creates a dynamic number of arrays, each with a dynamic name that will hold some element parts from a declared array. I am attempting to split a single array into multiple arrays that will hold an equal number of elements.

dec_array = (abc10 def2 ghi333 jkl mno51 pqr_6) # this array will always have some multiple of three elements but variable in the number of elements
num_elem=3
num_arr=0 # will eventually incr and loop once this works
for ((i=0; i < ${#dec_array[@]}; i+=num_elem)); do
    part=( "${dec_array[@]:i:num_elem}" )
    echo "Elements in first example: ${part[*]}"
    dyn_array_${num_arr}=( "${dec_array[@]:i:num_elem}" )
    echo "Elements in second example: $dyn_array_${num_arr}[*]}"
done

output:

Elements in first example: abc10 def2 ghi333
Elements in first example: jkl mno51 pqr_6

syntax error near unexpected token `"${dec_array[@]:i:num_elem}"'
`    dyn_array_${num_arr}=( "${dec_array[@]:i:num_elem}" )'

I need to be able to split dec_array into multiple dyn_array_n, e.g.,

dyn_array_0: abc10 def2 ghi333
dyn_array_1: jkl mno51 pqr_6

What is the correct syntax to accomplish creating a dynamic number of arrays with dynamic names?

like image 838
kmt Avatar asked Aug 30 '25 16:08

kmt


2 Answers

With recent version of bash:

echo $BASH_VERSION 
5.1.4(1)-release

There is at least 3 differents ways for this:

  1. Using declare -a directly:
    declare -a "prefix_${midlepart}_suffix[variableIndex]=Content"
  2. Using printf in same way:
    printf -v "prefix_${midlepare}_suffix[variableIndex]" '%s' "Some string"
  3. As already suggested elsewhere using nameref:
    declare -n this="prefix_${midlepare}_suffix";this[variableIndex]=Content
  4. By using eval, but ...

1. Using declare simply:

1.1 Strictly answering request:

dec_array=(abc10 def2 ghi333 jkl mno51 pqr_6 stu_XYZ)
num_elements=${#dec_array[@]}
elements_per_array=3
for ((i=0;i<num_elements;i+=elements_per_array)); do
    declare -a "dyn_array_$(( i / elements_per_array + 1
                              ))=(${dec_array[*]:i:elements_per_array})"
done

declare -p ${!dyn_array_*}
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
declare -a dyn_array_3=([0]="stu_XYZ")

1.1.1 More robust supporting spaces and other special characters

dec_array=(abc10 def2 ghi333 jkl mno51 pqr_6 stu_XYZ hello\ world.)
num_elements=${#dec_array[@]}
elements_per_array=3

for ((i=0;i<num_elements;i+=elements_per_array)); do
    part=("${dec_array[@]:i:elements_per_array}")
    declare -a "dyn_array_$(( i / elements_per_array + 1 ))=(${part[*]@Q})"
done

declare -p ${!dyn_array_*}
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
declare -a dyn_array_3=([0]="stu_XYZ" [1]="hello world.")

1.2 General use

answer=42
declare  -a "dyn_arr_$((2*21))+=(foo)"
declare  -a "dyn_arr_$((84/2))+=(bar\ baz)"
declare  -a "dyn_arr_$answer+=($'Hello\nworld..\n')"
declare  -a "dyn_arr_$((12))+=({A..I})"

Then for quick showing content of all arrays:

declare -p ${!dyn_arr_*}
declare -a dyn_arr_12=([0]="A" [1]="B" [2]="C" [3]="D" [4]="E" [5]="F" [6]="G" [
7]="H" [8]="I")
declare -a dyn_arr_42=([0]="foo" [1]="bar baz" [2]=$'Hello\nworld..\n')

1.3 Building a matrix:

for i in {0..5}; do
     a=($((RANDOM%2)){,,,,})
     declare -a "mat_$i=(${a[*]@Q})
done

declare -p ${!mat_*}
declare -a mat_0=([0]="1" [1]="0" [2]="1" [3]="0" [4]="1" [5]="0")
declare -a mat_1=([0]="0" [1]="0" [2]="0" [3]="0" [4]="1" [5]="1")
declare -a mat_2=([0]="0" [1]="1" [2]="1" [3]="0" [4]="1" [5]="1")
declare -a mat_3=([0]="0" [1]="0" [2]="1" [3]="1" [4]="1" [5]="1")
declare -a mat_4=([0]="1" [1]="0" [2]="1" [3]="0" [4]="1" [5]="1")
declare -a mat_5=([0]="0" [1]="1" [2]="0" [3]="1" [4]="1" [5]="0")
. <(echo "printf ' | %s |\n'" \"\$\{mat_{0..5}\[\*\]\}\")
 | 1 0 1 0 1 0 |
 | 0 0 0 0 1 1 |
 | 0 1 1 0 1 1 |
 | 0 0 1 1 1 1 |
 | 1 0 1 0 1 1 |
 | 0 1 0 1 1 0 |

2. Alternatively, you could use printf:

2.1 Strictly answering request:

dec_array=(abc10 def2 ghi333 jkl mno51 pqr_6 stu_XYZ hello\ world.)
num_elements=${#dec_array[@]}
elements_per_array=3

for ((i=0;i<num_elements;i+=elements_per_array));do
    for ((l=0;l<$elements_per_array;l++));do
        (( i+l >num_elements )) && break
        printf -v "dyn_array_$(( i / elements_per_array + 1 ))[l]"  "%s" "${dec_array[i+l]}"
    done
done

declare -p ${!dyn_*}
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
declare -a dyn_array_3=([0]="stu_XYZ" [1]="hello world.")

2.2 General usage

for i in {0..5}; do
    for l in {0..5}; do
       printf -v randomLetter %03o $((RANDOM%26+65))
       printf -v "mat_$i[l]" %b \\$randomLetter
    done
done
declare -p ${!mat_*}

could render something like:

declare -a mat_0=([0]="W" [1]="V" [2]="P" [3]="V" [4]="E" [5]="O")
declare -a mat_1=([0]="Y" [1]="C" [2]="X" [3]="L" [4]="U" [5]="C")
declare -a mat_2=([0]="P" [1]="C" [2]="J" [3]="W" [4]="U" [5]="A")
declare -a mat_3=([0]="R" [1]="M" [2]="V" [3]="H" [4]="W" [5]="R")
declare -a mat_4=([0]="S" [1]="U" [2]="E" [3]="U" [4]="X" [5]="F")
declare -a mat_5=([0]="U" [1]="F" [2]="G" [3]="C" [4]="E" [5]="I")

3. Using nameref:

3.1 Strictly answering request:

dec_array=(abc10 def2 ghi333 jkl mno51 pqr_6 stu_XYZ $'foo bar\nbaz')
num_elements=${#dec_array[@]}
elements_per_array=3
for ((i=0;i<num_elements;i+=elements_per_array)); do
    declare -n crtArry=dyn_array_$(( i / elements_per_array + 1 ))
    crtArry=("${dec_array[@]:i:elements_per_array}")
done

declare -p ${!dyn_array_*}
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
declare -a dyn_array_3=([0]="stu_XYZ" [1]=$'foo bar\nbaz')

3.2 For a quick matrix:

for i in {0..5}; do
    declare -n crtArry=mat_$i
    crtArry=($((RANDOM%2)){,,,,})
done

declare -p ${!mat_*}
declare -a mat_0=([0]="1" [1]="0" [2]="1" [3]="1" [4]="0")
declare -a mat_1=([0]="0" [1]="0" [2]="0" [3]="0" [4]="1")
declare -a mat_2=([0]="1" [1]="1" [2]="0" [3]="0" [4]="1")
declare -a mat_3=([0]="1" [1]="1" [2]="1" [3]="0" [4]="1")
declare -a mat_4=([0]="1" [1]="0" [2]="0" [3]="1" [4]="0")
declare -a mat_5=([0]="1" [1]="1" [2]="1" [3]="0" [4]="0")

3.3 Using nameref in a function:

Nameref is useful for functions:

define_dyn() {
    local -n this=dyn_array_$1
    shift
    this=("$@")
}

for ((i=0;i<num_elements;i+=elements_per_array)); do
    define_dyn $((i/elements_per_array+1)) "${dec_array[@]:i:elements_per_array}"
done

declare -p ${!dyn_array*}
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
declare -a dyn_array_3=([0]="stu_XYZ" [1]=$'foo bar\nbaz')
get_dyn_elem() {
    local -n this=dyn_array_$1
    echo "${this[$2]}"
}

get_dyn_elem 3 1
foo bar
baz
like image 200
F. Hauri Avatar answered Sep 02 '25 12:09

F. Hauri


With bash 4.3+ we can use nameref's to dynamically create variables (including arrays) on the fly.

There are a few ways to slice-n-dice this; for this answer I'll figure out the number of new arrays up front and then as we traverse the dec_array[] array we'll fill in the new arrays.

#############
# +1 element (stu_XYZ) to demonstrate when element count != multiple of 3:

dec_array=(abc10 def2 ghi333 jkl mno51 pqr_6 stu_XYZ) 

num_elements="${#dec_array[@]}"
elements_per_array=3

#############
# we want ceiling(num_elements / elements_per_array):

num_arrays="$(( (num_elements + elements_per_array - 1) / elements_per_array ))"

index=0

for ((i=1; i<=num_arrays; i++))
do
    declare -n _arr="dyn_array_$i"                       # nameref
    _arr=()                                              # initialize the new array

    for ((j=1; j<=elements_per_array; j++))
    do
        (( index >= num_elements)) && break 2            # once we run out of elements we break out of both for loops
        _arr+=( "${dec_array[index]}" )                  # append current element to _arr[]
        (( index++ ))
    done
done

Verifying results and showing how to access the arrays later in your script:

for ((i=1; i<=num_arrays; i++))
do
    declare -n _arr="dyn_array_$i"
    echo "############### ${!_arr}"
    typeset -p "${!_arr}"                                # verify results

    for ((j=0; j<${#_arr[@]}; j++))                      # loop through each new array
    do
         echo "dyn_array_$i[$j] = ${_arr[j]}"            # "${_arr[j]}" ==> individual element reference
    done
done

NOTE: the 2nd echo is printing 4 strings: dyn_array_$i[ + $j + ] = + ${_arr[j]}

This generates:

############### dyn_array_1
declare -a dyn_array_1=([0]="abc10" [1]="def2" [2]="ghi333")
dyn_array_1[0] = abc10
dyn_array_1[1] = def2
dyn_array_1[2] = ghi333
############### dyn_array_2
declare -a dyn_array_2=([0]="jkl" [1]="mno51" [2]="pqr_6")
dyn_array_2[0] = jkl
dyn_array_2[1] = mno51
dyn_array_2[2] = pqr_6
############### dyn_array_3
declare -a dyn_array_3=([0]="stu_XYZ")
dyn_array_3[0] = stu_XYZ
like image 33
markp-fuso Avatar answered Sep 02 '25 10:09

markp-fuso