Array of arrays in bash
I struggled with this but found an uncomfortable compromise. In general, when faced with a problem whose solution involves using data structures in Bash, you should switch to another language like Python. Ignoring that advice and moving right along:
My use cases usually involve lists of lists (or arrays of arrays) and looping over them. You usually don't want to nest much deeper than that. Also, most of the arrays are strings that may or may not contain spaces, but usually don't contain special characters. This allows me to use not-to-confusing syntax to express the outer array and then use normal bash processing on the strings to get a second list or array. You will need to pay attention to your IFS delimiter, obvi.
Thus, associative arrays can give me a way to create a list of lists like:
declare -A JOB_LIST=(
[job1] = "a set of arguments"
[job2] = "another different list"
...
)
This allows you to iterate over both arrays like:
for job in "${!JOB_LIST[@]}"; do
/bin/jobrun ${job[@]}
done
Ah, except that the output of the keys list (using the magical ${!...}
) means that you will not traverse your list in order. Therefore, one more necessary hack is to sort the order of the keys, if that is important to you. The sort order is up to you; I find it convenient to use alphanumerical sorting and resorting to aajob1 bbjob3 ccjob6
is perfectly acceptable.
Therefore
declare -A JOB_LIST=(
[aajob1] = "a set of arguments"
[bbjob2] = "another different list"
...
)
sorted=($(printf '%s\n' "${!JOB_LIST[@]}"| /bin/sort))
for job in "${sorted[@]}"; do
for args in "${job[@]}"; do
echo "Do something with ${arg} in ${job}"
done
done
Bash has no support for multidimensional arrays. Try
array=(a b c d)
echo ${array[1]}
echo ${array[1][3]}
echo ${array[1]exit}
For tricks how to simulate them, see Advanced Bash Scripting Guide.
Field nest box in bash but it can not circumvent see the example.
#!/bin/bash
# requires bash 4 or later; on macOS, /bin/bash is version 3.x,
# so need to install bash 4 or 5 using e.g. https://brew.sh
declare -a pages
pages[0]='domain.de;de;https'
pages[1]='domain.fr;fr;http'
for page in "${pages[@]}"
do
# turn e.g. 'domain.de;de;https' into
# array ['domain.de', 'de', 'https']
IFS=";" read -r -a arr <<< "${page}"
site="${arr[0]}"
lang="${arr[1]}"
prot="${arr[2]}"
echo "site : ${site}"
echo "lang : ${lang}"
echo "prot : ${prot}"
echo
done
Knowing that you can split string into "array". You could creat a list of lists. Like for example a list of databases in DB servers.
dbServersList=('db001:app001,app002,app003' 'db002:app004,app005' 'dbcentral:central')
# Loop over DB servers
for someDbServer in ${dbServersList[@]}
do
# delete previous array/list (this is crucial!)
unset dbNamesList
# split sub-list if available
if [[ $someDbServer == *":"* ]]
then
# split server name from sub-list
tmpServerArray=(${someDbServer//:/ })
someDbServer=${tmpServerArray[0]}
dbNamesList=${tmpServerArray[1]}
# make array from simple string
dbNamesList=(${dbNamesList//,/ })
fi
# Info
echo -e "\n----\n$someDbServer\n--"
# Loop over databases
for someDB in ${dbNamesList[@]}
do
echo $someDB
done
done
Output of above would be:
----
db001
--
app001
app002
app003
----
db002
--
app004
app005
----
dbcentral
--
central