Trapping getopt invalid options

16,491

Solution 1

This sort of style works for me:

params="$(getopt -o d:h -l diff:,help --name "$cmdname" -- "$@")"

if [ $? -ne 0 ]
then
    usage
fi

eval set -- "$params"
unset params

while true
do
    case $1 in
        -d|--diff)
            diff_exec=(${2-})
            shift 2
            ;;
        -h|--help)
            usage
            exit
            ;;
        --)
            shift
            break
            ;;
        *)
            usage
            ;;
    esac
done

Solution 2

I found this to work as the last item in the getopts case statement:

*) eval echo "Unrecognized arg \$$[OPTIND-1]"; usage; exit ;;

Solution 3

This is not the most robust solution, but it's reasonable; it relies on the following:

  • The error message that getopt prints is prefixed with "getopt: "
  • The assumption is that it's acceptable to pass through a cleaned-up version of getopt's error message, augmented with custom information.

Code snippet:

# Invoke getopt; suppress its stderr initially.
args=$(getopt -o $SHORT_OPTIONS -l $LONG_OPTIONS -- "$@" 2>/dev/null)
if [[ $? -ne 0 ]]; then # getopt reported failure
    # Rerun the same getopt command so we can capture stderr output *only* this time.
    # Inefficient (and a potential maintenance headache, if literals were involved), but this will only execute in case of invalid input.
    # Alternatively, redirect the first getopt invocation's stderr output to a temp. file and read it here.
    errmsg=$(getopt -o $SHORT_OPTIONS -l $LONG_OPTIONS -- "$@" 2>&1 1>&-)
    # Strip getopt's prefix and augment with custom information.
    echo -e "${errmsg#getopt: }\nTry './cmd.sh --help for more information." 1>&2
    exit 1
fi

Solution 4

Do you have to use getopt at all? If you just use

while [ $# -gt 0 ]; do
  case "$1" in
    -d|--diff)
       diff_exec=(${2-})
       shift
       ;;
    -h|--help)
       usage
       exit
       ;;
     --)
       break
       ;;
     *)
       usage
       ;;
    esac
    shift
done

Then you own code is doing the checking.

Share:
16,491
Tom Auger
Author by

Tom Auger

Developer, designer, business owner. jQuery, AS3, WordPress, Perl, PHP

Updated on June 04, 2022

Comments

  • Tom Auger
    Tom Auger about 2 years

    I'm using getopt (not getops) to provide the ability for my bash script to process options and switches (both long --option and short -o forms).

    I'd like to be able to trap invalid options and handle them, typically echoing out that the user should try cmd --help and then exiting the script.

    Thing is, the invalid options are being caught by getopt, which is itself outputting a message such as "getopt: invalid option -- 'x'"

    Here's the pattern I'm using to set my getopt parameters:

    set -- $(getopt -o $SHORT_OPTIONS -l $LONG_OPTIONS -- "$@")
    

    where both $LONG_OPTIONS and $SHORT_OPTIONS are a comma-delimited list of options.

    Here's how I handle processing the options:

     while [ $# -gt 0 ]
        do
            case "$1" in
                -h|--help)
                    cat <<END_HELP_OUTPUT
    
        Help
        ----
    
        Usage: ./cmd.sh 
    
        END_HELP_OUTPUT
    
                    shift;
                    exit
                    ;;
                --opt1)
                    FLAG1=true
                    shift
                    ;;
                --opt2)
                    FLAG2=true
                    shift
                    ;;
                --)
                    shift
                    break
                    ;;
                *)
                    echo "Option $1 is not a valid option."
                    echo "Try './cmd.sh --help for more information."
                    shift
                    exit
                    ;;
            esac
        done
    

    getopt -q will suppress the output, but my trapping scheme within the case statement still fails to do what I expect. Instead, the program just executes, despite the invalid arguments.

  • mklement0
    mklement0 about 12 years
    @TomAuger: If you think that "pretty fragile" adequately paraphrases "not the most robust, but it's reasonable", you must be running an incompatible English-language shell. Invariably, trying to get information out of getopt reliably that it wasn't designed to report programmatically will be tricky. That said, the part about assuming that stderr output comes first is indeed shaky, so I've revised the code to address that. If you how to capture stdout and stderr into separate variables from a single command execution (not involving explicit creation of temporary files), let me know.
  • Rhys Ulerich
    Rhys Ulerich almost 12 years
    Using ':' as the first character within optstring suppresses the message output. However, the return code from getopt(1) is still nonzero and the unrecognized option does not get output by getopt(1).
  • jarno
    jarno over 7 years
    In which case *) is reached?
  • l0b0
    l0b0 over 7 years
    @jarno If you ever have a mismatch between your case statement and the getopt call, it will be caught there. It's just defensive programming.