2

In the following code, I create some associative arrays in a loop. The consist of two strings, a string identifier and a year. After creation, I want to access the arrays in a loop based on the identifier only.

#!/bin/bash

# Declare associative arrays of journal-year combinations
A_JOURNAL_LIST={JF,JFE,RFS}
B_JOURNAL_LIST={JBF,JFI,JMCB}
ALL_JOURNAL_LIST={JF,JFE,RFS,JBF,JFI,JMCB}
for year in {1998..2000} {2009..2011}
do
  eval "A_$year=($A_JOURNAL_LIST-$year) ;"
  eval "B_$year=($B_JOURNAL_LIST-$year) ;"
  eval "all_$year=($ALL_JOURNAL_LIST-$year) ;"
done  

Here I easily get bunch of arrays of the form A_1999 which e.g. expands to JF-1999 JFE-1999 RFS-1999 and so on.

for journal in A B all
do
  echo "${'$journal'_1999[@]}"
done

I expect

JF-1999 JFE-1999 RFS-1999
JBF-1999 JFI-1999 JMCB-1999
JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999

I got a bad substitution error all the time and I tried a lot of combinations. What's wrong?

0

3 Answers 3

4

Welcome to eval hell! Once you start using it, you never get rid of it.

for journal in A B all
do
  eval "echo \"\${${journal}_1999[@]}\""
done

There might be a much better way to do it, but I never bother with associative or otherwise nested arrays in shell scripts. If you need such data structures, you might be better off with a scripting language that supports them natively.

Actually, bash has support for associative arrays after a fashion. Whether they can be useful to you is another question, it's not portable to other shells in any case.

1
  • 1
    Good suggestion to look for another language: pick the right tool for the job. Commented Jan 29, 2015 at 14:54
3

variable indirection will be helpful here:

for journal in A B all
do
    indirect="${journal}_1999[@]"
    echo "$journal: ${!indirect}"
done

outputs

A: JF-1999 JFE-1999 RFS-1999
B: JBF-1999 JFI-1999 JMCB-1999
all: JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999

An eval-free rewrite. Arrays of arrays is not something bash is natively suited for, so I have to use space-separated strings and temp storage

# Declare associative arrays of journal-year combinations
a_journal_list=( {JF,JFE,RFS} )
b_journal_list=( {JBF,JFI,JMCB} )
all_journal_list=( "${a_journal_list[@]}" "${b_journal_list[@]}" )
declare -a a b all

for year in {1998..2000} {2009..2011}
do
    # store year-specific values as space-separated strings
    a[$year]=$( printf "%s-$year " "${a_journal_list[@]}" )
    b[$year]=$( printf "%s-$year " "${b_journal_list[@]}" )
    all[$year]=$( printf "%s-$year " "${all_journal_list[@]}" )
done  

selected_years=( 1998 1999 2000 )
for journal in a b all
do
    # I'll use the positional params for temp storage of the accumulated array
    set --
    for year in "${selected_years[@]}"
    do
        indirect="${journal}[$year]"
        # variable is unquoted to allow word splitting
        set -- "$@" ${!indirect}
    done
    echo $journal
    printf "%s\n" "$@"
done
2
  • That's an almost better way (one outside of the eval hell). How can I make indirect consist of more then one year? E.g. indirect="${journal}_1998[@] ${journal}_1999[@] ${journal}_2000[@]"? Commented Jan 29, 2015 at 15:08
  • You cannot, but I've posted an alternative Commented Jan 29, 2015 at 15:21
0

I think there is some misunderstood bash' arrays:

Arrays

   Bash provides one-dimensional indexed and associative array variables.  Any variable may be used as  an
   indexed  array; the declare builtin will explicitly declare an array.  There is no maximum limit on the
   size of an array, nor any requirement that members be indexed or assigned contiguously.  Indexed arrays
   are  referenced  using  integers  (including  arithmetic  expressions)  and are zero-based; associative
   arrays are referenced using arbitrary strings.  Unless otherwise noted, indexed array indices  must  be
   non-negative integers.

   An  indexed  array  is  created automatically if any variable is assigned to using the syntax name[sub‐
   script]=value.  The subscript is treated as an arithmetic expression that must evaluate  to  a  number.
   To  explicitly  declare  an  indexed  array,  use  declare  -a name (see SHELL BUILTIN COMMANDS below).
   declare -a name[subscript] is also accepted; the subscript is ignored.

   Associative arrays are created using declare -A name.

   Attributes may be specified for an array variable  using  the  declare  and  readonly  builtins.   Each
   attribute applies to all members of an array.

   Arrays  are  assigned  to  using  compound assignments of the form name=(value1 ... valuen), where each
   value is of the form [subscript]=string.  Indexed array assignments do not require anything but string.
   When  assigning  to  indexed arrays, if the optional brackets and subscript are supplied, that index is
   assigned to; otherwise the index of the element assigned is the last index assigned to by the statement
   plus one.  Indexing starts at zero.

   When assigning to an associative array, the subscript is required.

   This  syntax  is  also  accepted  by the declare builtin.  Individual array elements may be assigned to
   using the name[subscript]=value syntax introduced above.  When assigning to an indexed array,  if  name
   is  subscripted  by  a  negative number, that number is interpreted as relative to one greater than the
   maximum index of name, so negative indices count back from the end of the array, and  an  index  of  -1
   references the last element.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.