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!

6
  • stackoverflow.com/questions/16553089/… Commented Jul 24, 2016 at 14:58
  • Not trying to dynamically name a variable. Trying to dynamically name an array during loop runtime. Commented Jul 24, 2016 at 14:59
  • also unix.stackexchange.com/questions/288886/… Commented Jul 24, 2016 at 14:59
  • Which specific version of bash? Both 4.0 and 4.3 add pertinent facilities. Commented Jul 24, 2016 at 15:27
  • 1
    (Aside: You shouldn't be using all-caps for your variable names; see pubs.opengroup.org/onlinepubs/009695399/basedefs/…, 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). Commented Jul 24, 2016 at 15:28

1 Answer 1

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")'
Sign up to request clarification or add additional context in comments.

4 Comments

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?
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!
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).
@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 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.