Maths noob
Maths noob

Reputation: 1802

bash: pass associative array as a parameter to another script

I want to be able to do:

Script1.sh

declare -A map=(
  ['A']=1
  ['B']=2
  ['C']=3
  ['D']=4
)

sh script2.sh ???

Script2.sh

params = ...
echo ${params['A']}

ie, access parameters by keys. I have seen related questions for normal arrays and the answer to them has been to pass the array as:

sh script2.sh "${AR[@]}"

which I believe translates to:

sh script2.sh  "${map[0]}" "${map[1]}" "${map[2]}"

But with that, I can only access the elements based on their order.

Is there a clever trick to achieve what I want? perhaps with something that passes on "A=1" "B=2" "C=3" "D=4" instead and have script2.sh parse them? or is there a neater solution?

Upvotes: 1

Views: 1222

Answers (2)

Paul Hodges
Paul Hodges

Reputation: 15358

If you are only calling script2.sh from inside script1.sh, then all you need to do (as @markp-fuso pointed out) is source script2.sh and it will run in the current context with all the data already loaded.

If you really want it to be on the command line, then pass it as key=val and have your code in script2.sh check each of it's args for that format and set them in an associative array.

declare -A map=()
for arg in "$@"
do if [[ "$arg" =~ ^[A-Z]=[0-9]$ ]] # more complex k/v will get ugly
   then map[${arg/=?}]=${arg/?=}    # as will the assignment w/o eval
   fi
done
# And finally, just to see what got loaded -
declare -p map

$: script2.sh C=3 A=1
declare -A map=([A]="1" [C]="3" )

As mentioned above, a more complicated possible set of key names and/or values will require a suitably more complex test as well as assignment logic. Clearly, for anything but the simplest cases, this is going to quickly get problematic.

Even better, set up a full getopts loop, and pass your args with proper indicators. This takes more design and more implementation, but that's what it takes to get more functionality.

Upvotes: 3

markp-fuso
markp-fuso

Reputation: 34808

Assumptions:

  • the array is the only item being passed to script2 (this could be relaxed but would likely require adding some option flag processing to script2)
  • the array name will always be map (could probably make this dynamic but that's for another day)
  • the array indices and values do not contain any special/control characters (eg, line feeds) otherwise passing the array structure on the command line to script2 gets mucho complicated really quick (there are likely some workarounds for this scenario, too)

Some basic components:

The array named map:

$ declare -A map=(
  ['A']=1
  ['B']=2
  ['C']=3
  ['D']=4
)

Use typeset to generate a command to (re)produce the contents of array map:

$ typeset -p map
declare -A map=([A]="1" [B]="2" [C]="3" [D]="4" )

From here we can pass the typeset output to script2 and then have script2 evaluate the input, eg:

$ cat script1
echo "in script1"
declare -A map=(
  ['A']=1
  ['B']=2
  ['C']=3
  ['D']=4
)
./script2 $(typeset -p map)

$ cat script2
echo "in script2"
echo " \$@ = $@"
eval "$@"
for i in "${!map[@]}"
do
        echo "${i} : ${map[${i}]}"
done

Running script1 generates:

$ ./script1
in script1
in script2
 $@ = declare -A map=([A]="1" [B]="2" [C]="3" [D]="4" )
A : 1
B : 2
C : 3
D : 4

I know, I know, I know ... eval == evil. I'll have to think about a replacement for eval ... also open to suggestions.

Upvotes: 2

Related Questions