Search code examples
bashgetopts

Bash getopts multiple flags with single required parameter


I'm having trouble wrapping my head around how to make getopts accept multiple flags with a single argument. For example.

Flags: Children: a - Adam b - Beth c - Cathy d - Duncan

Required parameter: ice cream flavor

(not what my script really does, but easiest to spell out this way)

What I can do right now:

./flavor -a chocolate

./flavor -c chocolate 

What I'd like to do:

./flavor -acd chocolate

Rather than the only thing I can get it to do so far:

./flavor -a chocolate -c chocolate -d chocolate

Every variation I've tried with getopts either doesn't see the parameter at all or requires it after every single flag declaration. I feel like I'm missing something obvious that will give me the "aha" moment, but I'm not seeing it on my own. Can anyone point me in the right direction?

============================================

while getopts ":a:b:c:d:" opt; do
  case $opt in
        a)
          do stuff with $OPTARG
         ;;
        b) 
          do stuff with $OPTARG
         ;;
        c)
          do stuff with $OPTARG
         ;;
        d)
          do stuff with $OPTARG
         ;;
        :)
         echo "Option -$OPTARG requires an argument." >&2
         exit 1
         ;;
  esac
done

============================================

My apologies if this is a really dumb question and thank you for your time.


Solution

  • Stacking options that require arguments is not supported by getopts. You can only stack options that do not take arguments.

    To do what you propose, a lot more complication in the argument processing is required. Essentially you would have to pre-process the options to find out where the non-option arguments begin. This is not particularly beautiful or recommended, but this does literally what you asked for without requiring an additional option:

    _GETOPTS=":abcd"
    while getopts "${_GETOPTS}" opt
    do
      case $opt in
            abcd)
             :
             ;;
            \?)
             echo "Unsupported option (-${OPTARG})" >&2
             exit 1
             ;;
      esac
    done
    eval "FIRSTARG=\${${OPTIND}}"
    if [ -z "${FIRSTARG}" ]
    then
       echo "Flavor argument required."
       exit 1
    fi
    OPTIND=1
    while getopts "${_GETOPTS}" opt
    do
      case $opt in
            a)
             echo do a stuff with ${FIRSTARG}
             ;;
            b)
             echo do b stuff with ${FIRSTARG}
             ;;
            c)
             echo do c stuff with ${FIRSTARG}
             ;;
            d)
             echo do d stuff with ${FIRSTARG}
             ;;
      esac
    done
    

    Results:

    $ ./flavor -acd chocolate
    do a stuff with chocolate
    do c stuff with chocolate
    do d stuff with chocolate
    

    One can access other arguments after the first non-option argument, but that would take a bit more work.

    Note that handling the same set of options in multiple getopts statements is prone to create long term maintenance issues in that it might be too easy for the options handled by the two case statements to get out of sync.