5

I have this code in a tool I am currently building:

while [ $# -gt 0 ]; do
  case "$1" in
    --var1=*)
      var1="${1#*=}"
      ;;
    --var2=*)
      var1="${1#*=}"
      ;;
    --var3=*)
      var1="${1#*=}"
      ;;
    *)
      printf "***************************\n
              * Error: Invalid argument.*\n
              ***************************\n"
  esac
  shift
done

I have many options to add, but five of my options should be saved as arrays. So if I call the tool, let's say from the shell using something like this:
./tool --var1="2" --var1="3" --var1="4" --var1="5" --var2="6" --var3="7"

How can I save the value of var1 as an array? Is that possible? And, if so, what is the best way to deal with these arrays in terms of efficiency if I have too many of them?.

3

2 Answers 2

7

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[@]}"
1
  • 1
    Hi @Stéphane , thanks for your effort. Really amazing :) Commented Oct 22, 2015 at 14:50
-1

I would suggest you take a look at my General Shell Script GitHub: utility_functions.sh. There you will see a function called getArgs. It's designed for associating options and values.

I'm pasting here the function only, but it depends on a couple more functions inside that script

##########################
#
#   Function name: getArgs
#
#   Description:
#       This function provides the getopts functionality
#   while allowing the use of long operations and list of parameters.
#   in the case of a list of arguments for only one option, this list
#   will be returned as a single-space-separated list in one single string.
#
#   Pre-reqs:
#       None
#
#   Output:
#       GA_OPTION variable will hold the current option
#       GA_VALUE variable will hold the value (or list of values) associated
#           with the current option
#   
#   Usage:
#       You have to source the function in order to be able to access the GA_OPTIONS
#   and GA_VALUES variables
#       . getArgs $*
#
####################
function getArgs {

    # Variables to return the values out of the function
    typeset -a GA_OPTIONS
    typeset -a GA_VALUES

    # Checking for number of arguments
    if [[ -z $1 ]]
    then
        msgPrint -warning "No arguments found"
        msgPrint -info "Please call this function as follows: . getArgs \$*"
        exit 0
    fi

    # Grab the dash
    dash=$(echo $1 | grep "-")
    # Looking for short (-) or long (--) options
    isOption=$(expr index "$dash" "-")
    # Initialize the counter
    counter=0
    # Loop while there are arguments left
    while [[ $# -gt 0 ]]
    do
        if [[ $dash && $isOption -eq 1 ]]
        then
            (( counter+=1 ))
            GA_OPTIONS[$counter]=$1
            shift
        else
            if [[ -z ${GA_VALUES[$counter]} ]]
            then
                GA_VALUES[$counter]=$1
            else
                GA_VALUES[$counter]="${GA_VALUES[$counter]} $1"
            fi
            shift
        fi
        dash=$(echo $1 | grep "-")
        isOption=$(expr index "$dash" "-")
    done
    # Make the variables available to the main algorithm
    export GA_OPTIONS
    export GA_VALUES

    msgPrint -debug "Please check the GA_OPTIONS and GA_VALUES arrays for options and arguments"
    # Exit with success
    return 0
}

As you see, this particular function will export GA_OPTIONS and GA_VALUES. Only condition for this is that the values must be a space-separated list after the option.

You would call the script as ./tool --var1 2 3 4 5 --var2="6" --var3="7"

Or just use similar logic to accommodate your preferences.

2
  • 1
    getArgs $* would pass the result of the split+glob operator applied to the list of positional parameters to getArgs, it doesn't make any sense. Commented Oct 23, 2015 at 6:36
  • echo $1 | grep -, Would return the lines in the output of echo $1 (again $1 being subject to split+glob, and echo expanding sequences) that contain a dash. Commented Oct 23, 2015 at 6:38

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.