1

I have an array in my script that takes a list of group names. This list does not have a fixed number of elements.

GROUPS=(group1 group2 group3)

Each element in this set of GROUPS can have an unknown number of values. I dynamically create a set of arrays with a variable in the name where VARIABLE is the group name:

for VARIABLE in "${GROUPS[@]}"
do
  declare -a ARRAY_${VARIABLE}
done

I want to iterate through a set of values and if a condition is met add them to the array that they belong to as new elements.

for (( VALUES=1; VALUES<=10; VALUES++ ));
do 
  if the VALUE belongs to GROUPx assign it to the array for GROUPx 
    ARRAY_$VARIABLE+=("$VALUES")
  fi
done

This should end up with a set of arrays that contain values that belong the groups ie:

ARRAY_group1= 1 2 5 9 10
ARRAY_group2= 3 7 
ARRAY_group3= 4 6 8

But I always get bash: syntax error near unexpected token `"$VALUES"'. If I am just assigning it to an array called ARRAY it works perfectly but I have to assign values in my script to an unknown number of arrays that need to be created dynamically.

I could instead add the values to a string whose name contains a variable with concatenation and later use sed to select individual values but I cannot find the correct syntax for this expression either.

let TEST_$VARIABLE="$TEST_$VARIABLE:$VALUES"
let TEST_$VARIABLE="${TEST_$VARIABLE}:$VALUES"
let TEST_$VARIABLE="$((TEST_$VARIABLE)):$VALUES"
let TEST_$VARIABLE="$((TEST_$VARIABLE))" ":$VALUES"

I have tried a few other things but they all run up against the exact same issue, bash really doesn't like variables being used in the variable name but I must create them dynamically which means I have to have a variable in the name.

I have tried a few other things but if you know how to refer to a variable that contains a variable in its name reliably in bash so I can avoid all of the syntax errors and bad substitution errors I would greatly appreciate it.

Tldr: Is there some syntax that I can use to get bash to treat ARRAY_$VARIABLE[@] in the same way that bash treats ARRAY[@]

  • IMO if you are doing things like this, it is time to start using a language that supports complex data structures. Perl or python, for example, if you want to stick with non-compiled scripting languages. Perhaps go or rust for compiled...and there's always C. and lisp or scheme. If you can't decide, pick one that's commonly used for the kind of software you want to write, and has a large selection of libraries for various tasks, and an active development and user community. – cas Feb 15 '18 at 3:50
  • For example to do what you are trying to do in perl, a commonly used data structure called a hash-of-arrays (HoA) would be used. see man perllol and man perldsc for details, but in short, you'd have a hashed array %groups, with each element containing either a scalar (single value) or array. e.g. $groups{group1} = (1,2,5,9,10);. The array in ${groups{group1} can be worked with and manipulated like any other array using standard functions like push, pop, shift, unshift etc. – cas Feb 15 '18 at 3:58
1

namerefs

I think a nameref would be what you want:

name=foo
declare -a array_$name
declare -n target=array_$name
for x in asdf 'white space' ; do target+=("$x"); done 
declare -p array_$name

outputs:

declare -a array_foo=([0]="asdf" [1]="white space")

eval, declare, let

The other, worse, ways would be to use eval or declare to set the values.

eval gets tricky with the quoting, you don't want to accidentally expand any $s inside your values.

eval "array_$name+=(\"\$x\")"

declare makes it a bit iffy to implement appending to the array. It's rather straightforward though if you know the index to assign to:

declare "array_$name[$i]=$x"

(Getting the number of elements in array_$name seems to require a nameref or eval again.)

You mentioned let; it's a bit like declare here, but works with arithmetic expressions so isn't a good fit for the general case.

name=a; 
let "test_$name=12+34"        # sets test_a to 46
let "test_$name=foo"          # reads the _value_ of variable foo!
let "test_$name=foo bar"      # this is an error.

alternative data structures

Alternatively, if there can only be one of each value, invert the data structure, and store the group id of each value in a single array:

for (( val=1; val <= 10; val++ )); do
     groupid=$(find_groupid "$val")
     variables[$val]=$groupid
done

Then walk over the whole array and ignore the groups you're not interested in.

Or, if your values don't contain any white space, just use strings for the inner arrays. To loop over them, do something like

for group in "${groups[@]}"; do 
    for item in $group; do       # no quotes here, we want wordsplitting
        ...

(or use an associative array there to store the group names too.)

  • In your nameref section did you mean typeset -n instead of declare -n? I see that nameref -n is included in bash 4.3 but our environment is 4.2 so its not an option for me. I greatly appreciate this though and will try to use some of your other suggestions to do my task. It does look like nameref would be the solution to my trouble though, I wish I could use it in bash 4.2. – Michael Lafleur Feb 14 '18 at 23:04
  • @MichaelLafleur, typeset and declare are the same in Bash, the manual lists declare as the primary one. ksh has just typeset, though, so compatibility might be a reason to use that. Namerefs would really help here, so it might be worth checking if you can upgrade... – ilkkachu Feb 14 '18 at 23:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.