Home > front end >  getopts & preventing accidentally interpreting short options as arguments
getopts & preventing accidentally interpreting short options as arguments

Time:05-14

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

  • Related