Passing named arguments as array in shell script

If on Linux (with the util-linux utilities including getopt installed, or the one from busybox), you can do:

declare -A opt_spec
var1=() var2=() var4=false
unset var3
opt_spec=(
  [opt1:]='var1()' # opt with argument, array variable
  [opt2:]='var2()' # ditto
  [opt3:]='var3'   # opt with argument, scalar variable
  [opt4]='var4'    # boolean opt without argument
)
parsed_opts=$(
  IFS=,
  getopt -o + -l "${!opt_spec[*]}" -- "$@"
) || exit
eval "set -- $parsed_opts"
while [ "$#" -gt 0 ]; do
  o=$1; shift
  case $o in
    (--) break;;
    (--*)
      o=${o#--}
      if ((${opt_spec[$o]+1})); then # opt without argument
        eval "${opt_spec[$o]}=true"
      else
        o=$o:
        case "${opt_spec[$o]}" in
          (*'()') eval "${opt_spec[$o]%??}+=(\"\$1\")";;
          (*) eval "${opt_spec[$o]}=\$1"
        esac
        shift
      fi
  esac
done
echo "var1: ${var1[@]}"

That way, you can call your script as:

my-script --opt1=foo --opt2 bar --opt4 -- whatever

And getopt will do the hard work of parsing it, handling -- and abbreviations for you.

Alternatively, you could rely on the type of the variable instead of specifying it in your $opt_spec associative array definition:

declare -A opt_spec
var1=() var2=() var4=false
unset var3
opt_spec=(
  [opt1:]=var1   # opt with argument
  [opt2:]=var2   # ditto
  [opt3:]=var3   # ditto
  [opt4]=var4    # boolean opt without argument
)
parsed_opts=$(
  IFS=,
  getopt -o + -l "${!opt_spec[*]}" -- "$@"
) || exit
eval "set -- $parsed_opts"
while [ "$#" -gt 0 ]; do
  o=$1; shift
  case $o in
    (--) break;;
    (--*)
      o=${o#--}
      if ((${opt_spec[$o]+1})); then # opt without argument
        eval "${opt_spec[$o]}=true"
      else
        o=$o:
        case $(declare -p "${opt_spec[$o]}" 2> /dev/null) in
          ("declare -a"*) eval "${opt_spec[$o]}+=(\"\$1\")";;
          (*) eval "${opt_spec[$o]}=\$1"
        esac
        shift
      fi
  esac
done
echo "var1: ${var1[@]}"

You can add short options like:

declare -A long_opt_spec short_opt_spec
var1=() var2=() var4=false
unset var3
long_opt_spec=(
  [opt1:]=var1   # opt with argument
  [opt2:]=var2   # ditto
  [opt3:]=var3   # ditto
  [opt4]=var4    # boolean opt without argument
)
short_opt_spec=(
  [a:]=var1
  [b:]=var2
  [c]=var3
  [d]=var4
)
parsed_opts=$(
  IFS=; short_opts="${!short_opt_spec[*]}"
  IFS=,
  getopt -o "+$short_opts" -l "${!long_opt_spec[*]}" -- "$@"
) || exit
eval "set -- $parsed_opts"
while [ "$#" -gt 0 ]; do
  o=$1; shift
  case $o in
    (--) break;;
    (--*)
      o=${o#--}
      if ((${long_opt_spec[$o]+1})); then # opt without argument
        eval "${long_opt_spec[$o]}=true"
      else
        o=$o:
        case $(declare -p "${long_opt_spec[$o]}" 2> /dev/null) in
          ("declare -a"*) eval "${long_opt_spec[$o]}+=(\"\$1\")";;
          (*) eval "${long_opt_spec[$o]}=\$1"
        esac
        shift
      fi;;
    (-*)
      o=${o#-}
      if ((${short_opt_spec[$o]+1})); then # opt without argument
        eval "${short_opt_spec[$o]}=true"
      else
        o=$o:
        case $(declare -p "${short_opt_spec[$o]}" 2> /dev/null) in
          ("declare -a"*) eval "${short_opt_spec[$o]}+=(\"\$1\")";;
          (*) eval "${short_opt_spec[$o]}=\$1"
        esac
        shift
      fi
  esac
done
echo "var1: ${var1[@]}"