Can getopts parse a subset of a bash script's arguments and leave the rest intact?

bash, getopts

Solution

Try the following, which only requires the script's own options to be known in advance:

#!/usr/bin/env bash

passThru=() # init. pass-through array
while getopts ':cb:' opt; do # look only for *own* options
  case "$opt" in
    b)
      file="$OPTARG";;
    c) ;;
    *) # pass-thru option, possibly followed by an argument
      passThru+=( "-$OPTARG" ) # add to pass-through array
      # see if the next arg is an option, and, if not,
      # add it to the pass-through array and skip it
      if [[ ${@: OPTIND:1} != -* ]]; then
        passThru+=( "${@: OPTIND:1}" )
        (( ++OPTIND ))
      fi
      ;;
  esac
done
shift $((OPTIND - 1))
passThru+=( "$@" )  # append remaining args. (operands), if any

./"$file" "${passThru[@]}"

Caveats: There are two types of ambiguities that cannot be resolved this way:

For pass-thru options with option-arguments, this approach only works if the argument isn't directly appended to the option. E.g., `-a val_a` works, but `-aval_a` wouldn't (in the absence of `a:` in the `getopts` argument, this would be interpreted as an option group and turn it into multiple options `-a`, `-v`, `-a`, `-l`, `-_`, `-a`).

As chepner points out in a comment on the question, `-a -b` could be option `-a` with option-argument `-b` (that just happens to look like an option itself), or it could be distinct options `-a` and `-b`; the above approach will do the latter.

To resolve these ambiguities, you must stick with your own approach, which has the down-side of requiring knowledge of all possible pass-thru options in advance.

Problem

I am using `getopts` to parse arguments in a `bash` script. I want to do two things: - remove processed options from `"$@"` - leave unprocessed options in `"$@"` consider the command-line ``` $ foo -a val_a -b val_b -c -d -e -f val_f positional_l positional_2 ... ``` Where `foo` uses `getopts` to parse options defined by a optstring of `'b:c'` and afterwards needs to leave `"$@"` as ``` `-a val_a -d -e -f val_f positional_l positional_2 ...` ``` I need to do two things: - parse a subset of options that may be given - leave all other opptions intact The reason for this is because `foo` must use the options it recognises to determine another script `bar` to which it must pass the remaining `"@"`. Normally `getopts` stops when it encounters an unrecognised option but I need it to continue (up to any `--`). I need it to proceess and remove the options it recognises and leave alone those that it doesn't. I did try to work around my problem using `--` between the `foo` options and the `bar` options but `getopts` seems to baulk if the text following `--` begins with a `-` (I tried but could not escape the hyphen). Anyway I would prefer not to have to use `--` because I want the existence of `bar` to be effectively transparent to the caller of `foo`, and I'd like the caller of `foo` to be able to present the options in any order. I also tried listing all `bar`options in `foo` (i.e. using `'a:b:cdef:'`for the optstring) without processing them but I need to delete the processed ones from `"$@"` as they occur. I could not work out how to do that (`shift` doesn't allow a position to be specified). I can manually reconstruct a new options list (see my own answer) but I wondered if there was a better way to do it.

Original source