3

I have an array

FIRST_ARRAY=(NEWYORK CALIFORNIA TEXAS)

A script that accepts a state can return cities in that state

For example the following would return:

user@localhost:~$ search NEWYORK cities
newyorkcity
buffalo
albany

user@localhost:~$ search CALIFORNIA cities
sanfrancisco 
paloalto 
losangeles

user@localhost:~$ search TEXAS cities
houston 
dallas 
austin

I would like to iterate over FIRST_ARRAY

for state in ${FIRST_ARRAY[@]}
   do
     cities=`search ${FIRST_ARRAY[state]} cities`
     ARRAY_$state=($cities}
done

At the end I would expect the following arrays to have been created and they would contain the following values

ARRAY_NEWYORK=(newyorkcity buffalo albany)
ARRAY_CALIFORNIA=(sanfrancisco paloalto losangeles)
ARRAY_TEXAS=(houston dallas austin)

If this works, then for example, I would like to be able to access austin by calling my the dynamically created array in the following way

echo ${ARRAY_TEXAS[2]}

Thanks!

ARL
  • 986
  • 2
  • 12
  • 25
  • http://stackoverflow.com/questions/16553089/bash-dynamic-variable-names – sozkul Jul 24 '16 at 14:58
  • Not trying to dynamically name a variable. Trying to dynamically name an array during loop runtime. – ARL Jul 24 '16 at 14:59
  • also http://unix.stackexchange.com/questions/288886/bash-array-values-like-variables-inside-loop – Sundeep Jul 24 '16 at 14:59
  • Which **specific** version of bash? Both 4.0 and 4.3 add pertinent facilities. – Charles Duffy Jul 24 '16 at 15:27
  • 1
    (Aside: You shouldn't be using all-caps for your variable names; see http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html, fourth paragraph, for POSIX specs re: environment variable naming conventions [indicating that the lower-case namespace is reserved for application use, whereas OS and shell tools will use all-upper-case]; since shell variable and environment variable names exist in the same namespace, that convention is applicable to shell variables as well). – Charles Duffy Jul 24 '16 at 15:28
  • BTW, you realize `${FIRST_ARRAY[state]}` is going to evaluate `state` numerically -- as `0` -- in all these cases, and thus will always refer to NEWYORK? – Charles Duffy Jul 24 '16 at 15:47

1 Answers1

3

On bash 4.0 or newer, with readarray / mapfile available, the following serves as a terse and correct implementation:

for state in "${FIRST_ARRAY[@]}"; do
    readarray -t "ARRAY_$state" < <(search "$state" cities)
done

In bash 4.3, a safe, literal translation of your code is available:

for state in "${FIRST_ARRAY[@]}"; do
    readarray -t cities < <(search "$state" cities)

    # make "dest" a namevar -- an alias -- for your intended destination
    # skip to next state if current state name is invalid
    # otherwise we could be assigning to an utterly different "dest"
    declare -n dest="ARRAY_$state" || continue

    # assign to that namevar
    dest=( "$cities" )

    # and discard it
    unset -n dest
done

In bash 3.x, doing this safely requires some printf %q magic to prepare content to be parsed by eval:

for state in "${FIRST_ARRAY[@]}"; do

    # why this, and not array=( $cities )? Try a city with spaces in its name.
    # or look at what happens when you have a "city name" that's a wildcard.
    cities=( )
    while read -r city; do
        cities+=( "$city" )
    done < <(search "$state" cities)

    # generate eval-safe replacement for the cities array
    printf -v cities_str '%q ' "${cities[@]}"

    # extra paranoia: make sure we fail with a parse error rather than doing something
    # unexpected if the state name is not a valid shell variable
    printf -v eval_str 'ARRAY_%q=( %s )' "$state" "$cities_str"

    eval "$eval_str" # and evaluate that
done

The original question didn't provide an implementation of search to make answers testable. For this answer, I'm using the following:

search() {
  case $1 in
    NEWYORK) printf '%s\n' newyorkcity buffalo albany ;;
    CALIFORNIA) printf '%s\n' sanfrancisco paloalto losangeles ;;
    TEXAS) printf '%s\n' houston dallas austin ;;
  esac
}

With the above defined, results can be verified as follows:

$ declare -p ARRAY_NEWYORK ARRAY_CALIFORNIA ARRAY_TEXAS
declare -a ARRAY_NEWYORK='([0]="newyorkcity" [1]="buffalo" [2]="albany")'
declare -a ARRAY_CALIFORNIA='([0]="sanfrancisco" [1]="paloalto" [2]="losangeles")'
declare -a ARRAY_TEXAS='([0]="houston" [1]="dallas" [2]="austin")'
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Charles, I should have pointed out that I am using BASH 4.1, that could have saved your a lot of trouble. My apologies in advance (although its handy to know how to apply this solution in older situations too). 'search' is an inhouse cli that queries mongodb. Is there some other information I could provide to assist more? – ARL Jul 24 '16 at 16:47
  • Your first solution was bang on and gave me exactly the result I was hoping for. Now that I know that the inverted array is the solution, I can read up on a bit and understand better how it works. Thank you very much for your time and effort in such an informative answer! – ARL Jul 24 '16 at 16:53
  • My apologies -- the "inverted array" mention in the first paragraph was actually part of an amendment I ended up realizing was unnecessary and removing. (This would have provided an alternative to `${FIRST_ARRAY[state]}` that actually mapped back to the index, but on taking a closer look I realized that that expansion could simply be replaced with `$state`). – Charles Duffy Jul 24 '16 at 17:00
  • 1
    @ARL, ...as for anything else you could have provided, I think you might include something akin to the implementation of `search` I gave in my answer in future questions (when those questions rely on an in-house tool), such that there's enough content provided to let folks run their proposed answers and verify that they work (the V given in MCVE at http://stackoverflow.com/help/mcve). Describing the behavior in enough detail to allow a mock implementation to be built was sufficient (and greatly appreciated!), but providing it yourself would have been even better. – Charles Duffy Jul 24 '16 at 17:04