8

In a script I have an associative array like:

declare -A my_vars=( ["key1"]="value1" ["key2"]="value" )

Is there a single command to transform that into a parameter list in the form

--key1=value1 --key2=value2

without having to manually re-write

 --key1="${VARS[key1]}" --key2="${VARS[key2]}"

the use-case I had in mind was to pass the array to a script as a list of parameters, like

my_script.sh $(to_param_list $VARS)

To expand on the comment I made on @Kusalananda answer, my exact use case is as follows: I have a script which is used to build a self-extracting installer using makeself, and this script receives some parameters which are to be separated between:

  • parameters for the script itself
  • parameters for the installer inside the self-extracting installer

The scripts then builds the installer like this:

to_param_list installer_param_list installer_param_array
./makeself ./path/to/sourcedir ./path/to/created/installer "My installer" ./path/to/install/inside/package "${installer_param_list[@]}"

However, I have tested the parameter passing with a very simple installer script inside the package:

while ! -z "$1" ; do
    echo "$1"
    shift
done

and passing an array like:

installer_param_array=( ["upgrade-from"]="19 .2.0" ["upgrade-to"]="19.3.0" )

results in this output:

--upgrade-to=19.3.0
--upgrade-from=19
.2.0
11

With a helper function:

#!/bin/bash

to_param_list () {
    declare -n outlist=$1
    declare -n inhash=$2

    for param in "${!inhash[@]}"; do
        outlist+=( "--$param=${inhash[$param]}" )
    done
}

declare -A my_vars=( ["key1"]="value1" ["key2"]="value" )

to_param_list list my_vars
my_script.sh "${list[@]}"

The final command in the above script would expand to the equivalent of having written

my_script.sh "--key2=value" "--key1=value1"

The to_param_list function takes the name of an array variable and the name of an associative array variable and uses these to create two "name reference" variables in the function (namerefs were introduced in bash release 4.3). These are then use to populate the given array variable with the keys and values in the appropriate format from the associative array.

The loop in the function iterates over "${!inhash[@]}", which is the list of individually quoted keys in your associative array.

Once the function call returns, the script would use the array to call your other script or command.

Running the above with

declare -A my_vars=( ["key1"]="hello world" ["key2"]="some thing" ["key3"]="* * *" )

to_param_list list my_vars
printf 'Arg: %s\n' "${list[@]}"

the script would output

Arg: --key2=some thing
Arg: --key3=* * *
Arg: --key1=hello world

This shows that the options are generated without word splitting or filename globbing coming into effect. It also shows that the order of the keys may not be preserved since accessing the keys out of an associative array will do so in a fairly random order.


You can't really use a command substitution safely here as the result of it would be a single string. If unquoted, this string would then be split on whitespace characters (by default), which would additionally split both the keys and values of your associative array. The shell would also perform filename globbing on the resulting words. Double quoting the command substitution would not help as that would result in calling your my_script.sh with a single argument.


Regarding your issue with makeself:

The makeself script does this with the arguments to your installer script:

SCRIPTARGS="$*"

This saves the arguments as a string in $SCRIPTARGS (concatenated, separated by spaces). This later gets inserted into the self-extracting archive as-is. For the options to be parsed correctly when they are re-evaluated (which they are when running the installer), you will have to provide an extra set of quotes in the values of the parameters for them to be properly delimited.

installer_param_array=( ["upgrade-from"]="'19 .2.0'" ["upgrade-to"]="'19.3.0'" )

Note that this is not a bug in my code. It's just a side effect of makeself producing shell code based on user-supplied values.

Ideally, the makeself script should have written each of the provided arguments with an extra set of quotes around them, but it doesn't, presumable because it's difficult to know what effect that may have. Instead, it leaves it to the user to provide these extra quotes.

Rerunning my test from above, but now with

declare -A my_vars=( ["key1"]="'hello world'" ["key2"]="'some value'" ["key3"]="'* * *'" )

to_param_list list my_vars
printf 'Arg: %s\n' "${list[@]}"

produces

Arg: --key2='some value'
Arg: --key3='* * *'
Arg: --key1='hello world'

You can see that these strings, when re-evaluated by the shell, would not get split on spaces.

Obviously, you could use your initial associative array and instead add the quotes in the to_param_list function by changing

outlist+=( "--$param=${inhash[$param]}" )

into

outlist+=( "--$param='${inhash[$param]}'" )

Either of these changes to your code would include the single quotes in the values of the options, so a re-evaluation of the values would become necessary.

  • Thanks, I'm gonna try that right away. – Matteo Tassinari Mar 15 at 9:25
  • I tried your solution, however if one of the values in the starting associative array contains any space, the resulting command will break up – Matteo Tassinari Mar 15 at 14:09
  • @MatteoTassinari No it would not. If it does, then you have forgotten the double quotes in "--$param=${inhash[$param]}" or in "${list[@]}", or the script that receives the options does something wrong in parsing them. – Kusalananda Mar 15 at 14:40
  • 1
    No, it's not that, I'll try to edit my question to better show my use-case. – Matteo Tassinari Mar 15 at 17:29
  • 1
    I added my actual use-case and the error I'm having – Matteo Tassinari Mar 16 at 9:16

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.