Count number of elements in bash array, where the name of the array is dynamic (i.e. stored in a variable)

19,247

you should handle that stuff in the index evals. and you can indirect through your indirection variable's indices if you make it an array.

a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for   i in   0 1 2 3 4 5
do    c=
      printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
      printf "\n\tindex is $i and count is $c\n\n"
done

<abc1>

    index is 0 and count is 5

<def2>

    index is 1 and count is 5

<ghi3>

    index is 2 and count is 5

<jkl4>

    index is 3 and count is 5

<mno5>

    index is 4 and count is 5

<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>

    index is 5 and count is 5

Because bash's indices are 0-based, the total count of array objects will always work out to one more than than the highest set index, and so:

c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"

this index is unset 5

...the parameter expands out to the default word if any is provided.

If one is not provided:

c=
${!r}
echo "$c"

5

...there's no harm done.

In the loop I track an $index variable and check if it is at least as large as $count. When it is lesser I expand the $reference var to a[i] because it is a valid index, but when it is equal or greater I expand the $ref to the entire $array.

Here it is in a function:

ref_arr(){
    local    index=-1 count=
    local    ref=(   "$1[ count= \${#$1[@]}  ]"
                     "$1[ index ]"    "$1[ @ ]"
    )  &&    printf  "input array '%s' has '%d' members.\n" \
                     "$1"  "${!ref-${count:?invalid array name: "'$1'"}}"
    while    [ "$((index+=1))" -lt "$count"  ]
    do       printf  "$1[$index]  ==  '%s'\n"  "${!ref[1]}"
    done
}
some_array=(some "dumb
            stuff" 12345\'67890 "" \
          '$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'

input array 'some_array' has '5' members.
some_array[0]  ==  'some'
some_array[1]  ==  'dumb
                stuff'
some_array[2]  ==  '12345'67890'
some_array[3]  ==  ''
some_array[4]  ==  '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'
Share:
19,247

Related videos on Youtube

drwatsoncode
Author by

drwatsoncode

Scientist by day, programmer by night. Sleep? I wish...

Updated on September 18, 2022

Comments

  • drwatsoncode
    drwatsoncode almost 2 years

    Brief statement of the question:

    Is there built-in bash method to count number of elements in bash array, where the name of the array is dynamic (i.e. stored in a variable), without resorting to making a fully copy of the array or using eval?

    More information:

    Using bash parameter substitution, one can do the following:

    • Determine the length of an array:
      myArr=(A B C); echo ${#myArr[@]}.
    • Indirectly reference a variable by name:
      NAME=myVar; echo ${!NAME}
      (this also applies to array elements):
      NAME=myArr[1]; echo ${!NAME}

    But if the name of an array is stored in another variable, how can one determine the number of elements in the array? (One might consider this a combination of the above two parameter substitutions.) For example:

    myArr=(A B C D)
    NAME=myArr
    # Get the number of elements in the array indirectly referenced by NAME.
    count=${#$NAME[@]}  # This syntax is invalid. What is the right way?
    

    Below are multiple attempts that all FAIL:

      # Setup for following attempts:
      myArr=(A B C D)
      NAME=myArr
      EXPR1=$NAME[@]          # i.e. EXPR1='myArr[@]'
      EXPR2=#$NAME[@]         # i.e. EXPR2='#myArr[@]'
    
      # Failed attempts to get the lengh of the array indirectly:
      1.  count=${#$NAME[@]}  # ERROR: bash: ...: bad substitution
      2.  count=${#!EXPR1}    # ERROR: bash: !EXPR}: event not found
      3.  count=${#\!EXPR1}   # ERROR: bash: ...: bad substitution
      4.  count=${!#EXPR1}    # ERROR: bash: ...: bad substitution
      5.  count=${!EXPR2}     # Returns NULL
    

    I've also tried some other variants of the above, but have not yet found anything that works without either: (A) making a copy of the array or (B) by using eval.

    Working Methods:

    There are a couple ways of solving this that are probably not optimal (but correct me if I'm wrong):

    Method 1: Copy the Array

    Assign the array to another (statically-named) variable and get the number of elements in it.

    EXPR=$NAME[@]
    arrCopy=( "${!EXPR}" )
    count=${#arrCopy}
    

    Method 2: Use eval

    EXPR="count=\${#$NAME[@]}"  # i.e. 'count=${myArr[@]}'
    eval $EXPR
    # Now count is set to the length of the array
    

    Summary:

    Is there any built-in method (i.e. parameter substitution syntax) in bash to determine the length of an array indirectly? If not, what is the most efficient way to do this? I assume it is the eval method above, but are there security or performance issues with eval?

  • drwatsoncode
    drwatsoncode over 8 years
    Thanks for replying, but your answer is what I already described under the section "Method 1: Copy the Array". The question also specifically stated that the array length should be determined "without resorting to making a fully copy of the array", which is exactly what you did.
  • drwatsoncode
    drwatsoncode over 8 years