How can I generate a list of arguments unacceptable to getopts in Bash?

292 Views Asked by At

Let's say I'm running getopts in a Bash script with the option string ":a:b" and I provide the following command line parameters:

./script.sh -a foo bar -b bar zappo

The instance of "foo" is an expected argument for the option a and the option b has no expected argument. However, both instances of "bar" and the instance of "zappo" are unacceptable to getopts. After running getopts, how can I echo a variable containing a list of all of the unacceptable arguments? How could I produce the list "bar bar zappo"? Thanks!

Here's some code for your convenience:

#!/bin.bash
option_string=":a:b"
unexpected_parameters=""
OPTIND=1
while getopts "${option_string}" options; do
    case ${options} in
        a)
            echo "-a triggered with parameter ${OPTARG}" >&2 # output to STDERR
            ;;
        b)
            echo "-b triggered" >&2 # output to STDERR
            ;;
        \?)
            echo "invalid option: -${OPTARG}" >&2 # output to STDERR
            ;;
    esac
    #<insert code magic... unexpected_parameters="${unexpected_parameters} "$(MAGIC)"">
done
echo "unexpected parameters: ${unexpected_parameters}"
1

There are 1 best solutions below

0
On

getopts stops processing at the first non-option argument. That's Posix-style argument processing. In Posix-style argument processing, given the command

utility -a foo bar -b bar zappo

utility will not interpret -b as a command-line flag. If -a takes an argument, then bar will be the first positional argument, and then there will be three more positional arguments, -b, bar, and zappo.

GNU extends this syntax by permuting command-line arguments so that flag options can be anywhere in the command-line. However, if you set the environment variable POSIXLY_CORRECT, then GNU utilities will (mostly) behave like normal Posix utilities. The GNU version of the Posix C library function getopt(3) (and getopt_long(3)) use GNU syntax by default, and also react appropriate to the POSIXLY_CORRECT environment variable.

However, the bash getopts builtin is strictly Posix-style. So in your case, with getopts ":a:b", your loop will only be executed once, for flag a with argument foo. When getopts terminates, OPTIND is set the the index of the first unconsumed command-line argument, in this case 3 (bar).

If you want to use getopts with GNU-style option handling, you have to do the permuting yourself. For example, you could do something like this:

# Positional arguments are not necessarily unexpected; in fact, they are usually
# expected. So I accumulate them here in a bash array
positional=()
while (($#)); do
  # If you really wanted to do GNU style processing, you'd need to special case
  # '--' and "long options" starting with '--'. Here, I just do the simplest
  # thing, which is to pass anything that vaguely looks like an optional argument
  # (starts with a -) to getopts.
  if [[ $1 = -* ]]; then
    # Grab some options:
    while getopts "$option_string" option; do
      case $option in
        a) ... ;;
        # etc.
      esac
    done
    # get rid of whichever options we've handled and reset OPTIND
    shift $((OPTIND - 1))
    OPTIND = 1
  else
    # Accumulate and ditch the positional argument:
    positional+=("$1")
    shift
  fi
done

Alternatively, you can use the GNU implementation of getopt(1), which will do all that for you, at the cost of a slightly more annoying interface. (At least, I find it annoying, but YMMV.)