Skip to main content

Create arrays with brace expansion in loop [Resolved]

I would like to generate a number of arrays that differ only by year. In the loop, I create the arrays with brace expansion and a variable.

I tried the following code without success:

LIST={JF,JFE,RFS,JBF,JFI,JMCB}
for year in {1998..2000} {2009..2011}
do
  declare -a 'y$year=('"$LIST"'-$year)'
  echo "${y$year[@]}"
done

The outcome should be the list of the following entries:

y1998: JF-1998 JFE-1998 RFS-1998 JBF-1998 JFI-1998 JMCB-1998
y1999: JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999
...
y2011: JF-2011 JFE-2011 RFS-2011 JBF-2011 JFI-2011 JMCB-2011

I don't need to print them, just use them in a loop as well as pass them as arguments. Because of the second, eval in the loop is not sufficient.


Asked April 21, 2017
Posted Under: Unix Linux
8 views
3 Answers

Deferring brace expansion is really a case for eval, particularly if you want to stringify things — ordinary parameter expansion doesn't do the right thing at the right time.

This should do what it seems you wanted:

LIST={JF,JFE,RFS,JBF,JFI,JMCB}
for year in {1998..2000} {2009..2011}
do
  eval "y$year=($LIST-$year)"
  tmp="y$year[@]"
  echo "${!tmp}"
done

You can't indirect into an array, so it's necessary to stringify the array inside an eval too if you want to print it out. If you don't, you can take out everything after the ;. tmp is used for indirect expansion: with tmp set to "y$year[@]", where $year is replaced with its value, the expansion ${!tmp} gives the contents of this iteration's array (what ${y1998[@]}, etc, would have expanded to).

The above will output:

JF-1998 JFE-1998 RFS-1998 JBF-1998 JFI-1998 JMCB-1998
JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999
JF-2000 JFE-2000 RFS-2000 JBF-2000 JFI-2000 JMCB-2000
JF-2009 JFE-2009 RFS-2009 JBF-2009 JFI-2009 JMCB-2009
JF-2010 JFE-2010 RFS-2010 JBF-2010 JFI-2010 JMCB-2010
JF-2011 JFE-2011 RFS-2011 JBF-2011 JFI-2011 JMCB-2011

and also create arrays y1998...y2011. The declares aren't strictly necessary, although they do let you skip an eval if you're aiming for that and don't need to stringify.

I suggest that this probably isn't the way you really want to achieve your underlying goal, though, whatever that is. Nested loops aren't evil, and if any bit of it's hardcoded you can abstract that out.


Answered April 21, 2017
 
I meant, originally, that ${!foo[@]} didn't give the values of the array named in foo in the way that indirection to a scalar does, and I discovered that foo='bar[@]' means that ${!foo} does what I wanted. – Michael Homer Dec 29 '14 at 2:18
 CanDoerz  1 week ago
 
If you have, say, - in IFS (the input field separator) everything gets cut up in the wrong place. The same happens if you have the default IFS and a space in one of your words. – Michael Homer Dec 29 '14 at 1:30
 CanDoerz  1 week ago
 
Yes, my intention is maybe not healthy, but everything works in my little script. What do you mean by $IFS? – MERose Dec 29 '14 at 1:28
 CanDoerz  1 week ago
 
Minimising escaped characters. All of it is definitely not something you should generally want to do for those reasons and others, yes. If you have an odd enough IFS value you're on your own. – Michael Homer Dec 29 '14 at 1:19
 CanDoerz  1 week ago
 
why do you not just eval "y$year=...; echo \"\${y$year[@]}\""? As written your command is susceptible to all kinds of unintended effects depending on the values of $IFS and filename expansion. – mikeserv Dec 29 '14 at 1:16
 CanDoerz  1 week ago

Here's one way (with brace expansion):

unset y _y
for y in {JF,JFE,RFS,JBF,JFI,JMCB,}-{{1998..2000},{2009..2011}}
do  case "${_y=y${y#*-}[@]}"               in 
    (y${y#-}*) echo "${!_y}"               ;; 
    (*)        declare -a "${_y%???}+=($y)";; 
esac; unset y _y; done

That will expand your parameter set for the for loop out to all of your desired values plus an extra last set which will consist only of the years. For each that does not begin with a digit declare adds an array member to whatever y${y##*[!0-9]} comes to, and for each that does, echo prints it.

So for the first 36 iterations it builds each array, and for the last 6 it prints each. The output is:

JF-1998 JFE-1998 RFS-1998 JBF-1998 JFI-1998 JMCB-1998
JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999
JF-2000 JFE-2000 RFS-2000 JBF-2000 JFI-2000 JMCB-2000
JF-2009 JFE-2009 RFS-2009 JBF-2009 JFI-2009 JMCB-2009
JF-2010 JFE-2010 RFS-2010 JBF-2010 JFI-2010 JMCB-2010
JF-2011 JFE-2011 RFS-2011 JBF-2011 JFI-2011 JMCB-2011

Here is an alternative, perhaps...

for year in 1998 1999 2000 2009 2010 2011
do  printf "%s-$year " JF JFE RFS JBF JFI JMCB
echo; done

That would at least get you the same output... To also store that in an array as well, probably you could...

for year in 1998 1999 2000 2009 2010 2011
do  declare -a "y$year=($(printf "%s-$year " JF JFE RFS JBF JFI JMCB |
               tee /dev/fd/2 ))"
    year=y$year[@]; year=(${!year})
    echo "${#year[@]}"
done 2>&1; unset year

This is the output from the second command, but the first prints the same sans the 6 - which just indicates the array member count.

JF-1998 JFE-1998 RFS-1998 JBF-1998 JFI-1998 JMCB-1998 6
JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999 6
JF-2000 JFE-2000 RFS-2000 JBF-2000 JFI-2000 JMCB-2000 6
JF-2009 JFE-2009 RFS-2009 JBF-2009 JFI-2009 JMCB-2009 6
JF-2010 JFE-2010 RFS-2010 JBF-2010 JFI-2010 JMCB-2010 6
JF-2011 JFE-2011 RFS-2011 JBF-2011 JFI-2011 JMCB-2011 6

Remember that declare is a command and the var$expand=(something $expand) argument is just that - an arg. declare's args are expanded in the same way as any other - which is not true of a var$expand=$expand statement. So you can indirectly declare in the same way you can indirectly export or whatever - the hardquotes you use are not necessary I think.


Answered April 21, 2017

Convert the brace expansion to an array via set --.

Then you have a simple two dimensional loop to construct your output:

set -- {JF,JFE,RFS,JBF,JFI,JMCB}
for year in {1998..2000} {2009..2011}; do
    printf "y%s: " $year
    for code; do
        printf "%s-%s " $code $year
    done
    echo
done

Yields:

y1998: JF-1998 JFE-1998 RFS-1998 JBF-1998 JFI-1998 JMCB-1998
y1999: JF-1999 JFE-1999 RFS-1999 JBF-1999 JFI-1999 JMCB-1999
y2000: JF-2000 JFE-2000 RFS-2000 JBF-2000 JFI-2000 JMCB-2000
y2009: JF-2009 JFE-2009 RFS-2009 JBF-2009 JFI-2009 JMCB-2009
y2010: JF-2010 JFE-2010 RFS-2010 JBF-2010 JFI-2010 JMCB-2010
y2011: JF-2011 JFE-2011 RFS-2011 JBF-2011 JFI-2011 JMCB-2011

Answered April 21, 2017
Your Answer
D:\Adnan\Candoerz\CandoProject\vQA