BASH: how to create a dynamic array name in a loop
Solution 1
You can use indirect expansion:
#!/bin/bash
# create the arrays
n=0
for i in aaa bbb ccc; do
let array$i[$n]=$(date "+%N")
((++n))
done
# display what we did
n=0
for i in aaa bbb ccc; do
var=array$i[$n]
echo ${!var}
((++n))
done
Note that this will only create array variables arrayaaa[0], arraybbb[1] and arrayccc[2], and not (as you might expect) arrayaaa[0], arrayaaa[1], arrayaaa[2], arraybbb[0], arraybbb[1], arraybbb[2], arrayccc[0], arrayccc[1], and arrayccc[2].
Solution 2
This is how you would create a dynamically named variable (bash version < 4.3).
# Dynamically named array
my_variable_name="dyn_arr_names"
eval $my_variable_name=\(\)
# Adding by index to the array eg. dyn_arr_names[0]="bob"
eval $my_variable_name[0]="bob"
# Adding by pushing onto the array eg. dyn_arr_names+=(robert)
eval $my_variable_name+=\(robert\)
# Print value stored at index indirect
echo ${!my_variable_name[0]}
# Print value stored at index
eval echo \${$my_variable_name[0]}
# Get item count
eval echo \${#$my_variable_name[@]}
Below is a group of functions that can be used to manage dynamically named arrays (bash version < 4.3).
# Dynamically create an array by name
function arr() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
# The following line can be replaced with 'declare -ag $1=\(\)'
# Note: For some reason when using 'declare -ag $1' without the parentheses will make 'declare -p' fail
eval $1=\(\)
}
# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
eval $1[\$\(\(\${#${1}[@]}\)\)]=\$2
}
# Update an index by position
function arr_set() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
eval ${1}[${2}]=\${3}
}
# Get the array content ${array[@]}
function arr_get() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
eval echo \${${1}[@]}
}
# Get the value stored at a specific index eg. ${array[0]}
function arr_at() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
[[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
local v=$1
local i=$2
local max=$(eval echo \${\#${1}[@]})
# Array has items and index is in range
if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
then
eval echo \${$v[$i]}
fi
}
# Get the value stored at a specific index eg. ${array[0]}
function arr_count() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
local v=${1}
eval echo \${\#${1}[@]}
}
array_names=(bob jane dick)
for name in "${array_names[@]}"
do
arr dyn_$name
done
echo "Arrays Created"
declare -a | grep "a dyn_"
# Insert three items per array
for name in "${array_names[@]}"
do
echo "Inserting dyn_$name abc"
arr_insert dyn_$name "abc"
echo "Inserting dyn_$name def"
arr_insert dyn_$name "def"
echo "Inserting dyn_$name ghi"
arr_insert dyn_$name "ghi"
done
for name in "${array_names[@]}"
do
echo "Setting dyn_$name[0]=first"
arr_set dyn_$name 0 "first"
echo "Setting dyn_$name[2]=third"
arr_set dyn_$name 2 "third"
done
declare -a | grep "a dyn_"
for name in "${array_names[@]}"
do
arr_get dyn_$name
done
for name in "${array_names[@]}"
do
echo "Dumping dyn_$name by index"
# Print by index
for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
do
echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"
done
done
for name in "${array_names[@]}"
do
echo "Dumping dyn_$name"
for n in $(arr_get dyn_$name)
do
echo $n
done
done
Below is a group of functions that can be used to manage dynamically named arrays (bash version >= 4.3).
# Dynamically create an array by name
function arr() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -g -a $1=\(\)
}
# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
declare -n r=$1
r[${#r[@]}]=$2
}
# Update an index by position
function arr_set() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
declare -n r=$1
r[$2]=$3
}
# Get the array content ${array[@]}
function arr_get() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
declare -n r=$1
echo ${r[@]}
}
# Get the value stored at a specific index eg. ${array[0]}
function arr_at() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
[[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
declare -n r=$1
local max=${#r[@]}
# Array has items and index is in range
if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
then
echo ${r[$2]}
fi
}
# Get the value stored at a specific index eg. ${array[0]}
function arr_count() {
[[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
declare -p "$1" > /dev/null 2>&1
[[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
declare -n r=$1
echo ${#r[@]}
}
array_names=(bob jane dick)
for name in "${array_names[@]}"
do
arr dyn_$name
done
echo "Arrays Created"
declare -a | grep "a dyn_"
# Insert three items per array
for name in "${array_names[@]}"
do
echo "Inserting dyn_$name abc"
arr_insert dyn_$name "abc"
echo "Inserting dyn_$name def"
arr_insert dyn_$name "def"
echo "Inserting dyn_$name ghi"
arr_insert dyn_$name "ghi"
done
for name in "${array_names[@]}"
do
echo "Setting dyn_$name[0]=first"
arr_set dyn_$name 0 "first"
echo "Setting dyn_$name[2]=third"
arr_set dyn_$name 2 "third"
done
declare -a | grep 'a dyn_'
for name in "${array_names[@]}"
do
arr_get dyn_$name
done
for name in "${array_names[@]}"
do
echo "Dumping dyn_$name by index"
# Print by index
for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
do
echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"
done
done
for name in "${array_names[@]}"
do
echo "Dumping dyn_$name"
for n in $(arr_get dyn_$name)
do
echo $n
done
done
For more details on these examples visit Getting Bashed by Dynamic Arrays by Ludvik Jerabek
Solution 3
Take refuge of eval:
n=0
for i in aaa bbb ccc; do
eval "array${i}[$n]=$(date "+%N")"
((++n))
done
Peter Pan
Updated on June 07, 2022Comments
-
Peter Pan almost 2 years
here's what I tried:
n=0 for i in aaa bbb ccc; do array${i}[$n]=$(date "+%N") ((++n)) done n=0 for i in aaa bbb ccc; do echo ${array${i}[$n]} ((++n)) done
any ideas how to make the dynamic array name work? thanks a lot!
-
Charles Duffy almost 11 yearsUsing eval without using
printf %q
to create eval-safe strings is more than a little risky. (Yes, in practice,date +%N
isn't likely to be generating anything unsafe... but in practice, this answer is likely to be used with data other thandate
). -
Adrian Frühwirth almost 11 years+1 for not using eval (alternatively you can use
typeset
instead oflet
). -
Peter Pan almost 11 yearsthank you, both let and eval do work. now my next problem is that the actual list consists of items like "1.1 1.2 mgmt" (interfaces) which are formatted as shown below. with such a list the loop gets broken: list=$(echo -e "1.1 1.2 mgmt") n=0 for i in $list; do let "array1_$i[$n]=$(date "+%N")" ((++n)) done n=0 for i in $list; do var=array1_$i[$n] echo ${!var} ((++n)) done thanks in advance for any ideas...
-
Peter Pan almost 11 yearsthank you, both let and eval do work. now my next problem is that the actual list consists of items like "1.1 1.2 mgmt" (interfaces) which are formatted as shown below. with such a list the loop gets broken: list=$(echo -e "1.1 1.2 mgmt") n=0 for i in $list; do let "array1_$i[$n]=$(date "+%N")" ((++n)) done n=0 for i in $list; do var=array1_$i[$n] echo ${!var} ((++n)) done thanks in advance for any ideas...
-
anubhava almost 11 yearsBash (and most other shells) don't allow
.
s (DOTs) in identifiers or a variable name so even this will fail:foo1.1='bar'
-
Sir Athos almost 11 years@Peter Pan: Can you edit your question to insert this additional information? If gives you a lot better formatting options.