Array of arrays in bash

ArraysBashShell

Arrays Problem Overview


I'm attempting to read an input file line by line which contains fields delimited by periods. I want to put them into an array of arrays so I can loop through them later on. The input appears to be ok, but 'pushing' that onto the array (inData) doesn't appear to be working.

The code goes :

Input file: 
GSDB.GOSALESDW_DIST_INVENTORY_FACT.MONTH_KEY
GSDB.GOSALESDW_DIST_INVENTORY_FACT.ORGANIZATION_KEY


infile=${1}
 
OIFS=$IFS
IFS=":"

cat ${infile} | while read line
do
      line=${line//\./:}
      inarray=(${line})
#      echo ${inarray[@]}
#      echo ${#inarray[@]}      
#      echo ${inarray[0]}
#      echo ${inarray[1]}
#      echo ${inarray[2]}
      
      inData=("${inData[@]}" "${inarray[@]}")
done 
IFS=$OIFS

echo ${#inData[@]}   

for ((i = 0; i < ${#inData[@]}; i++))
do
 echo $i
    for ((j = 0; j < ${#inData[$i][@]}; j++))
    do
       echo ${inData[$i][$j]}
    done
done

Arrays Solutions


Solution 1 - Arrays

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

Solution 2 - Arrays

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.

Solution 3 - Arrays

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

Solution 4 - Arrays

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

Solution 5 - Arrays

I use Associative Arrays and use :: in the key to denote depth. The :: can also be used to embed attributes, but that is another subject,...

declare -A __myArrayOfArray=([Array1::Var1]="Assignment" [Array2::Var1]="Assignment")

An Array under Array1

__myArrayOfArray[Array1::SubArray1::Var1]="Assignment"

The entries in any array can be retrieved (in order ...) by ...

local __sortedKeys=`echo ${!__myArrayOfArray[@]} | xargs -n1 | sort -u | xargs`
for __key in ${__sortedKeys}; do
    #
    # show all properties in the Subordinate Profile "Array1::SubArray1::"
    if [[ ${__key} =~ ^Array1::SubArray1:: ]]; then
        __property=${__key##Array1::SubArray1::}
        if [[ ${__property} =~ :: ]]; then
            echo "Property ${__property%%:*} is a Subordinate array"
        else
            echo "Property ${__property} is set to: ${__myArrayOfArray[${__key}]}"
        fi
    fi 
done

THE list of subordinate "Profiles" can be derived by:

declare -A __subordinateProfiles=()
local __profile
local __key
for __key in "${!__myArrayOfArray[@]}"; do
    if [[ $__key =~ :: ]]; then
        local __property=${__key##*:}
        __profile=${__key%%:*}
        __subordinateProfiles[${__profile}]=1
    fi   
done

Solution 6 - Arrays

A bash array of arrays is possible, if you convert and store each array as a string using declare -p (see my function stringify). This will properly handle spaces and any other problem characters in your arrays. When you extract an array, use function unstringify to rebuild the array. This script demonstrates an array of arrays:

#!/bin/bash 
# BASH array of arrays demo

# Convert an array to a string that can be used to reform 
# the array as a new variable. This allows functions to
# return arrays as strings. Works for arrays and associative
# arrays. Spaces and odd characters are all handled by bash 
# declare.
# Usage: stringify variableName
#     variableName - Name of the array variable e.g. "myArray",
#          NOT the array contents.
# Returns (prints) the stringified version of the array.
# Examples. Use declare to make an array:
#     declare -a myArray=( "O'Neal, Dan" "Kim, Mary Ann" )
# (Or to make a local variable replace declare with local.)
# Stringify myArray:
#     stringifiedArray="$(stringify myArray)"
# Reform the array with any name like reformedArray:
#     eval "$(unstringify reformedArray "$stringifiedArray")"
# To stringify an argument list "$@", first create the array
# with a name:     declare -a myArgs=( "$@" )
stringify() {
    declare -p $1
}

# Reform an array from a stringified array. Actually this prints
# the declare command to form the new array. You need to call 
# eval with the result to make the array.
# Usage: eval "$(unstringify newArrayName stringifiedArray [local])"
#     Adding the optional "local" will create a local variable 
#     (uses local instead of declare).
# Example to make array variable named reformedArray from 
# stringifiedArray:
#     eval "$(unstringify reformedArray "$stringifiedArray")"
unstringify() {
    local cmd="declare"
    [ -n "$3" ] && cmd="$3"
    # This RE pattern extracts 2 things:
    #     1: the array type, should be "-a" or "-A"
    #     2: stringified contents of the array 
    # and skips "declare" and the original variable name.
    local declareRE='^declare ([^ ]+) [^=]+=(.*)$'
    if [[ "$2" =~ $declareRE ]]
    then
        printf '%s %s %s=%s\n' "$cmd" "${BASH_REMATCH[1]}" "$1" "${BASH_REMATCH[2]}"
    else
        echo "*** unstringify failed, invalid stringified array:" 1>&2
        printf '%s\n' "$2" 1>&2
        return 1
    fi
}

# array of arrays demo
declare -a array # the array holding the arrays
declare -a row1=( "this is" "row 1" )
declare -a row2=( "row 2" "has problem chars" '!@#$%^*(*()-_=+[{]}"|\:;,.<.>?/' )
declare -a row3=( "$@" ) # row3 is the arguments to the script

# Fill the array with each row converted to a string.
# stringify needs the NAME OF THE VARIABLE, not the variable itself
array[0]="$(stringify row1)"
array[1]="$(stringify row2)"
array[2]="$(stringify row3)"

# Print array contents
for row in "${array[@]}"
do
    echo "Expanding stringified row: $row"
    # Reform the row as the array thisRow
    eval "$(unstringify thisRow "$row")"
    echo "Row values:"
    for val in "${thisRow[@]}"
    do
        echo "   '$val'"
    done
done

Solution 7 - Arrays

You could make use of (de)referencing arrays like in this script:

#!/bin/bash

OFS=$IFS     # store field separator
IFS="${2: }" # define field separator
file=$1      # input file name

unset a      # reference to line array
unset i j    # index
unset m n    # dimension

### input

i=0
while read line
do
  a=A$i
  unset $a
  declare -a $a='($line)'
  i=$((i+1))
done < $file
# store number of lines
m=$i

### output

for ((i=0; i < $m; i++))
do
  a=A$i
  # get line size
  # double escape '\\' for sub shell '``' and 'echo'
  n=`eval echo \\${#$a[@]}`
  for (( j = 0; j < $n; j++))
  do
    # get field value
    f=`eval echo \\${$a[$j]}`
    # do something
    echo "line $((i+1)) field $((j+1)) = '$f'"
  done
done

IFS=$OFS

Credit to https://unix.stackexchange.com/questions/199348/dynamically-create-array-in-bash-with-variables-as-array-name

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjaybeeView Question on Stackoverflow
Solution 1 - ArraysLukáš KřížView Answer on Stackoverflow
Solution 2 - ArrayschorobaView Answer on Stackoverflow
Solution 3 - ArraysNuxView Answer on Stackoverflow
Solution 4 - ArraysThe Big BabaView Answer on Stackoverflow
Solution 5 - ArraysoobashView Answer on Stackoverflow
Solution 6 - ArraysSteve ZobellView Answer on Stackoverflow
Solution 7 - ArrayssetemplerView Answer on Stackoverflow