1

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?

Maths noob
  • 1,684
  • 20
  • 42
  • 1
    ‘*I can only access the elements based on their order.*’ What more do you want?! – Biffen Sep 25 '20 at 12:14
  • 3
    Did you look up similar questions before - A simple search provided me 1. [How to pass an associative array as argument to a function in Bash?](https://stackoverflow.com/q/4069188/5291015) 2. [Passing associative array as argument with Bash](https://stackoverflow.com/q/17557434/5291015) – Inian Sep 25 '20 at 12:14
  • 6
    Note that `sh` doesn’t have arrays. – Biffen Sep 25 '20 at 12:15
  • @Inian That's a function inside the same script. little different right? – Maths noob Sep 25 '20 at 12:18
  • @Biffen "what more do you want"? I want people to call my script2.sh with arguments in random order and it still working just like my `script2.sh` shows?. as for sh not having arrays, I am still a noob to scripting. I meant bash. feel free to edit my question? – Maths noob Sep 25 '20 at 12:19
  • 3
    `params = ...` is invalid syntax or `sh` and `bash`. Did you check your code at https://shellcheck.net ? Is there a reason you're not using "shebang" lines at the top of each script to control what shell interprets your code (i.e. `#!/bin/bash`), rather than your current code, `bash script2` ? If readers edit your Q, then there's a risk of making a change that doesn't help define your problem. Clearly defining your problem is your responsibility, right? Good luck. – shellter Sep 25 '20 at 13:27
  • @shellter I have stated that I am new to scripting and I know close to zero. I have clearly stated the use case and the progress I have made. if there is a universal syntax for "here is pseudo code" you can suggest it. Until then, I will stick to `???` and `...` Thank you very much. Also, you can always make suggestions and I'd be happy to change my question. But unless it hasn't occurred to you, "Clearly defining your problem is your responsibility" isn't exactly encouraging folks to get into scripting. anyhow, thanks for your time. – Maths noob Sep 25 '20 at 13:44
  • 2
    We're here to help, but clearly defining your problem is your responsibility. Have patience and learn the best way to use the site (c.f. [this guide](https://stackoverflow.com/help/asking) for more), but if you are the one asking for help, you can't expect someone else to be responsible for guessing what you want. Stick with it. Do your part, and you will have thousands of years of user experience to guide you. Being careless and/or defensive will just make them surf away to a more interesting problem to solve. Remember, we aren't getting paid for this. Help us help you. – Paul Hodges Sep 25 '20 at 14:37
  • 1
    hmmmm ... `echo ${params['A']}` ... hardcoding of the index `'A'~ ` implies `script2` already knows the indices of the incoming array; I'm wondering if `script2` might be better defined as a function (either defined within `script1` or sourced/loaded from a file by `script1`); sure, this depends on what exactly `script2` is doing but if it has 'knowledge' specific to `script1` then it's not really a standalone script ... – markp-fuso Sep 25 '20 at 14:48
  • 2
    And it smells faintly of an [XY Problem](http://xyproblem.info/). – Paul Hodges Sep 25 '20 at 14:53
  • @markp-fuso In my use-case, I have a set of composable side-effects that I like to pick and choose and run in the order of choosing. I like my orchestrator scripts to be minimal and declarative and pass env-vars to these steps. right now, I am using this with exports and env-vars which is not ideal. – Maths noob Sep 25 '20 at 15:13
  • @PaulHodges This is not an algorithm question so I am not sure about the comparison. I am a developer and I value [my definition of] readability and familiar code above all else. I want to know if Bash supports this feature. – Maths noob Sep 25 '20 at 15:17
  • 1
    NOTE: I'm not familiar with `orchestrator scripts` so fwiw ... what you're defining sounds (to me) like a good use of functions; build an inventory of functions and then pick-n-choose just the ones you want; by executing the functions one after the other they will all work on the same 'local' data set thus eliminating the need to pass arrays between scripts ... – markp-fuso Sep 25 '20 at 15:47
  • @markp-fuso Thanks. let me think about it. – Maths noob Sep 25 '20 at 15:52
  • 1
    from the details listed above it sounds like the idea is something like: `script1` calls `script2` calls `script3` calls `script4`, each time passing a new set of array data to the child process; couple issues: a) will be hard(er) to pass an array 'up' the call stack and b) hardocoded `scriptX` calls will be a PITA to maintain if the ordering changes (eg, `script2` now calls `script4` which now calls `script3` => have to edit `script2` and `script3`); with functions the top-most shell sees all array/data changes and changing ordering is simply `script1; script2; script4; script3` – markp-fuso Sep 25 '20 at 16:43
  • all very valuable points and good to keep in mind. thanks @markp-fuso in my use case, I won't be doing "each time passing a new set of array data to the child process" my main mechanism for passing env-vars is through an env file, but I do some times need to override these and that's why I needed the arrays. fyi, each of this scripts is an invocation of newman: the postman CLI tool and this "orchestrator script" composes journeys for me. Glad I could get it to work. – Maths noob Sep 25 '20 at 20:00

2 Answers2

3

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.

tshalif
  • 2,277
  • 2
  • 15
  • 10
Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • 1
    depending on what else is in `script2` it may also be possible to just source `script2` (with no command line args) – markp-fuso Sep 25 '20 at 14:27
  • 1
    You can't export an array, not usefully, anyway. – chepner Sep 25 '20 at 14:51
  • 2
    [adding for OPs benefit] re: sourcing `script2` ... would (obviously) need to ensure no `exit` calls in `script2` otherwise the `exit` would be invoked in `script1` thus leading to `script1` terminating before any additional processing could be performed – markp-fuso Sep 25 '20 at 14:58
2

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.

markp-fuso
  • 28,790
  • 4
  • 16
  • 36
  • 1
    I prefer to write the output from the `typeset/declare` to a separate file and source it. There are advantages and disadvantages. This should work well, though. – Paul Hodges Sep 25 '20 at 14:45
  • yeah, I was thinking of that (`typeset -p` > file; feed file to `script2`) as an extended answer (especially) if the array indices or values contain special/control characters; several ways to slice-n-dice this one ... – markp-fuso Sep 25 '20 at 14:50
  • If you put double quotes like `./script2 "$(typeset -p map)"`, I think `eval` and `source` are exactly equivalent. Can you see any differences ? – Philippe Aug 25 '21 at 15:41