I am using getopts
to parse arguments in a bash
script. I want to do two things:
"$@"
"$@"
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:
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.
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.