How to prevent non-optarg args to Bash function?

51 views Asked by At

I've got a Bash function for using a select statement with git checkout. It works fine. I'd like to prevent a use case, and I'm not sure how. How can I prevent non-optarg values being passed to the function as "$1". For example:

  1. $ gco hello should no-op since hello is not associated with a -b flag. <-- Need to implement
  2. $ gco -b hello should check out the branch hello. <-- Working
  3. $ gco should present the select menu. <-- Working

As expected, checking for "$1" isn't the answer because it messes with potential optargs.

    if [ -z "$1" ]; then
        echo "$1"
        return
    fi
gco () {
    local branch=""

    while getopts :hb: opt; do
        case "$opt" in
            h) 
                echo "NAME:     gco - git branch to check out."
                echo "SYNOPSYS: gco [-b branch]"
                echo "-h        Display help text."
                echo "-b        branch"
                return
                ;;
            b) branch="$OPTARG" ;;
            :) echo "Missing argument for option -$OPTARG"; return 1 ;;
           \?) echo "Unknown option -$OPTARG"; return 1 ;;
        esac
    done

    if [ "$branch" != "" ]; then
        git checkout "$branch"
        return
    fi

    echo "Which branch would you like to check out?"
    select b in $(git branch | sed 's/* /  /'); do
        git checkout "$b"
        return
    done
}
2

There are 2 answers

0
Paul Pazderski On BEST ANSWER

Typical for using getopts is to 'shift' the parsed options after getopts finished. So after your while loop you can do

 shift $((OPTIND - 1))

to remove all parsed options. OPTIND indicates the next option getopts would check so after the loop it points to the first non-option. As example when calling your function with

gco -b master
  # OPTIND is 3 after the while loop
gco hello
  # OPTIND is 1 after the while loop

Now you actually can check for $1 if they are more non-opt args or in your case it is probably enough to

[[ $# == 0 ]] || return
0
Kaz On

Using a generic option processor alone, you cannot detect the case when an argument is missing the option. E.g. the user wanted gco -b hello but forgot the -b. The case gco hello looks like there are no options, followed by a non-option argument.

If the program has no non-option arguments, then that's the way the error will be handled.

gco: error, unexpected "hello" argument: option expected.

If the program has non-option arguments, but they have to have certain characteristics (like having to be names of existing files), then it will go that way:

gco: no such path: "hello".

Beyond that your program can try ad hoc heuristics. It can notice that a file was not found, but hello happens to be the name of a branch.

gco: no such path: "hello".
gco: there is a branch "hello". Did you mean -b "hello"?

or

gco: unexpected non-option argument: "hello"
gco: there is a branch "hello". Did you mean -b "hello"?

Option parsers typically deal with the options. They process known ones, flag unknown ones and then leave the non-option arguments for the program to handle.