Stephopolis
Stephopolis

Reputation: 1795

Bash: iteration through array names

I am trying to write code to break up a large array into many different small arrays. Eventually the array I would be passed is one of unknown size, this is just my test subject. I have gotten this far:

#!/bin/bash
num=(10 3 12 3 4 4)
inArray=${#num[@]}
numArrays=$(($inArray/2))
remain=$(($inArray%2))
echo $numArrays
echo $remain
nun=0
if test $remain -gt  $nun; then
        numArrays=$(($numArrays+1))
fi
array=(1 2)
j=0
for ((i=0;i<$numArrays;i++, j=j+2)); do
        array=("${num[@]:$j:2}")
        echo "The array says: ${array[@]}"
        echo "The size? ${#array[@]}"
done    

What I am really having a problem with is : I would like to make the variable 'array' be able to change names slightly every time, so each array is kept and has a unique name after the loop. I have tried making the name array_$i but that returns:

[Stephanie@~]$ ./tmp.sh 
3
0
./tmp.sh: line 16: syntax error near unexpected token `"${num[@]:$j:2}"'
./tmp.sh: line 16: `    array_$i=("${num[@]:$j:2}")'
[Stephanie@RDT00069 ~]$ ./tmp.sh 
3
0
./tmp.sh: line 16: syntax error near unexpected token `$i'
./tmp.sh: line 16: `    array($i)=("${num[@]:$j:2}")'

Does anyone have any advice? Thanks

Upvotes: 1

Views: 335

Answers (3)

chepner
chepner

Reputation: 531125

With simple variables, you can use the declare keyword to make indirect assignments:

v=foo
declare $v=5
echo $foo    # Prints 5

This doesn't extend to arrays in the obvious (to me, anyway) sense:

i=2
# This produces a syntax error
declare -a array_$i=("${num[@]:$j:2}")

Instead, you can declare an empty array

declare -a array_$i

or assign items one at a time:

declare -a array_$i[0]=item1 array_$i[1]=item2

Here's an example of using a for-loop to copy, say, the 3rd and 4th letters of a big array into a smaller one. We use i as the dynamic part of the name of the smaller array, and j as the index into that array.

letters=(a b c d e f)
i=1
j=0
for letter in "${letters[@]:2:2}"; do
    # E.g., i=0 and j=1 would result in
    #   declare -a array_0[1]=c
    declare -a array_$i[$j]=$letter
    let j+=1
  done
done

echo ${array_1[@]};  # c d

${foo[@]:x:y} gives us elements x, x+1, ..., x+y-1 from foo, and

You can wrap the whole thing inside another for-loop to accomplish the goal of splitting letters into 3 smaller arrays:

 # We'll create array_0, array_1, and array_2
for i in 0 1 2; do 
  # Just like our subset above, but start at position i*2 instead of
  # a constant.
  for letter in "${letters[@]:$((i*2)):2}"; do
      declare -a array_$i[$j]=$letter
  done
done

Once you manage to populate your three arrays, how do you access them without eval? Bash has syntax for indirect access:

v=foo
foo=5
echo ${!v}   # echoes 5!

The exclamation point says to use the word that follows as a variable whose value should be used as the name of the parameter to expand. Knowing that, you might think you could do the following, but you'd be wrong.

i=1
v=array_$i   # array_1
echo ${!v[0]}  # array_1[0] is c, so prints c, right? Wrong.

In the above, bash tries to find a variable called v[0] and expand it to get the name of a parameter to expand. We actually have to treat our array plus its index as a single name:

i=1
v=array_$i[0]
echo ${!v}    # This does print c

Upvotes: 1

kojiro
kojiro

Reputation: 77107

I don't think you can really avoid eval here, but you might be able to do it safely if you're careful. Here's my approach:

for name in "${!array_*}"; do # Get all names starting with array_
    i="${name#array_*}" # Get the part after array_
    if [[ $i != *[^0-9]* ]]; then # Check that it's a number.
        printf '%s is not a valid subarray name\n' "$name"
    else
        # Create a variable named "statement" that contains code you want to eval.
        printf -v statement 'cur_array=( "${%s[@]}" )' "$name"
        eval "$statement"
        # Do interesting things with $cur_array
    fi
done

Before this, when you're just creating the array, you know what $name should be, so just use the printf -v part.

To make it even safer, you could save all the allowed array names in another array and check that $name is a member.

Upvotes: 1

Nahuel Fouilleul
Nahuel Fouilleul

Reputation: 19315

This should work, but this is not a good solution, another language may be better bash does not support multi dimensional arrays

eval array_$i='('"${num[@]:$j:2}"')'

And then, for example

eval 'echo "${array_'$i'[0]}"'

Upvotes: 0

Related Questions