getopts printing help when no cmd. line argument was matched

Tomas Pruzina picture Tomas Pruzina · Mar 21, 2012 · Viewed 12k times · Source

I'm trying to use getopts in bash to parse command line arguments, but I couldn't figure out how to implement "default" action, if no argument was matched (or no cmdline argument given).

This is silghtly simplified version of what I've tried so far:

while getopts v:t:r:s:c: name;
do
        case $name in
        v) VALIDATE=1;;
        t) TEST=1;;
        r) REPORT=1;;
        s) SYNC=1;;
        c) CLEAR=1;;
        *) print_help; exit 2;;
        \?) print_help; exit 2;;
        esac
done

Is there any (simple) way to make it call print_help; exit 2; on non matching input?

Answer

David Souther picture David Souther · Mar 21, 2012

Looking between your question and the comments on Aditya's answer, I'd recommend the following:

[getopts]$ cat go
#!/bin/bash

function print_help { echo "Usage" >&2 ; } 

while getopts vtrsc name; do
    case $name in
        v) VALIDATE=1;;
        t) TEST=1;;
        r) REPORT=1;;
        s) SYNC=1;;
        c) CLEAR=1;;
        ?) print_help; exit 2;;
    esac
done

echo "OPTIND: $OPTIND"
echo ${#@}

shift $((OPTIND - 1))

while (( "$#" )); do
    if [[ $1 == -* ]] ; then
        echo "All opts up front, please." >&2 ; print_help ; exit 2
    fi
    echo $1
    shift
done

Since each of those are boolean flag options, you don't need (and in fact, do not want) the arguments, so we get rid of the colons. None of those characters are in IFS, so we don't need to wrap that in quotes, it will be one token for getopts anyway.

Next, we change the \? to a single ? and get rid of the *, as the * would match before the literal \?, and we might as well combine the rules into a single default match. This is a good thing, since any option specified with a - prefix should be an option, and users will expect the program to fail if they specify an option you don't expect.

getopts will parse up to the first thing that isn't an argument, and set OPTIND to that position's value. In this case, we'll shift OPTIND - 1 (since opts are 0-indexed) off the front. We'll then loop through those args by shifting them off, echoing them or failing if they start with a -.

And to test:

[getopts]$ ./go
OPTIND: 1
0
[getopts]$ ./go -t -v go go
OPTIND: 3
4
go
go
[getopts]$ ./go -d -v go go
./go: illegal option -- d
Usage
[getopts]$ ./go -t go -v go -d
OPTIND: 2
5
go
All opts up front, please.
Usage