Here's a toy example that shows what I mean:
while getopts "sf:e:" opt; foundOpts="${foundOpts}${opt}" ; done
echo $foundOpts
problem is that getopts
isn't particularly smart when it comes to arguments. for example, ./myscript.sh -ssssf
will output option requires an argument -- f
. That's good.
But if someone does ./myscript.sh -ssfss
by mistake, it parses that like -ss -f ss
, which is NOT desirable behavior!
How can I detect this? Ideally, I'd like to force that f
parameter to be defined separately, and allow -f foo
or -f=foo
, but not -sf=foo
or -sf foo
.
Is this possible without doing something hairy? I imagine I could do something with a RegEx, along the lines of match $@
against something like -[^ef[:space:]]*[ef]{1}[ =](.*)
, but I'm worried about false positives (that regex might not be exactly right)
Thanks!
CodePudding user response:
Here's what I've come up with. It seems to work, but I imagine the RegEx could be simplified. And I imagine there are edge cases I've overlooked.
validOpts="sf:e:"
optsWithArgs=$(echo "$validOpts" | grep -o .: | tr -d ':\n')
# ensure that options that require (or have optional) arguments are not combined with other short options
invalidOptArgFormats=( "[^$optsWithArgs[:space:]] [$optsWithArgs]" "[$optsWithArgs]([^ =]|[ =]$|$)" )
IFS="|" && invalidOptArgRegEx="-(${invalidOptArgFormats[*]})"
[[ "$@" =~ $invalidOptArgRegEx ]] && echo -e "An option you provided takes an argument; it is required to have its option\n on a separate - to prevent accidentally passing an opt as an argument.\n (You provided ${BASH_REMATCH[0]})" && exit 1
CodePudding user response:
I wouldn't do a regex-match against "$*"
directly because it isn't accurate, it's safer to loop through "$@"
.
Also, when you have -f -sf
, getopts
will treat -sf
as the argument of -f
.
Additionally, there's the case of --
which stops getopts
from parsing the rest of the arguments.
You should take all that into account when doing your safety checks:
#!/bin/bash
opts='sf:e:'
opts_with_args=$(printf %s "$opts" | sed -nE 's/[^:]*(.):/\1/gp')
for arg
do
[[ $arg == -- ]] && break;
[[ $is_optarg ]] && { is_optarg=''; continue; }
arg=${arg%%=*}
if [[ $arg == -*["$opts_with_args"]* ]]
then
if [[ "${#arg}" -gt 2 ]]
then
printf '%s\n' "error: $arg" 1>&2
exit 1
fi
is_optarg=1
fi
done
remark: I'm using sed
instead of grep -o
for portability reasons