Search code examples
bashscriptingflagscontinuegetopts

Is there a way to continue in a Flag when there is no $OPTARG set in Bash, GETOPTS?


I would like to build a script with getopts, that continues in the flag, when an $OPTARG isn't set. My script looks like this:

OPTIONS=':dBhmtb:P:'
while getopts $OPTIONS OPTION
do
        case "$OPTION" in
                m ) echo "m"          
                t ) echo "t"     
                d ) echo "d";;     
                h ) echo "h";; 
                B ) echo "b";;
                r ) echo "r";; 
                b ) echo "b"                                 
                P ) echo hi;; 
                    #continue here                    
                \? ) echo "?";;
                :) echo "test -$OPTARG requieres an argument" >&2
                         
        esac
done

My aim is to continue at my comment, when there is no $OPTARG set for -P. All I get after running ./test -P is : test -P requieres an argument and then it continues after the loop but I want to continue in the -P flag. All clear? Any Ideas?


Solution

  • First, fix the missing ;; in some of the case branches.

    I don't think you can: you told getopts that -P requires an argument: two error cases

    1. -P without an argument is the last option. In this case getops sees that nothing follows -P and sets the OPTION variable to :, which you handle in the case statement.

    2. -P is followed by another option: getopts will simply take the next word, even if the next word is another option, as OPTARG.

      Change the case branch to

      P ) echo "P: '$OPTARG'";;
      

      Then:

      • invoking the script like bash script.sh -P -m -t, the output is
        P: '-m'
        t
        
      • invoking the script like bash script.sh -Pmt, the output is
        P: 'mt'
        

      This is clearly difficult to work around. How do you know if the user intended the option argument to be literally "mt" and not the options -m and -t?

    You might be able to work around this using getopt (see the canonical example) using an optional argument for a long option (those require an equal sign like --long=value) so it's maybe easier to check if the option argument is missing or not.


    Translating getopts parsing to getopt -- it's more verbose, but you have finer-grained control

    die() { echo "$*" >&2; exit 1; }
    
    tmpArgs=$(getopt -o 'dBhmt' \
                     --long 'b::,P::' \
                     -n "$(basename "$0")" \
                     -- "$@"
            )
    (( $? == 0 )) || die 'Problem parsing options'
    eval set -- "$tmpArgs"
    
    while true; do
        case "$1" in
           -d)  echo d; shift ;;
           -B)  echo B; shift ;;
           -h)  echo h; shift ;;
           -m)  echo m; shift ;;
           -t)  echo t; shift ;;
          --P)  case "$2" in
                   '')  echo "P with no argument" ;;
                    *)  echo "P: $2" ;;
                esac
                shift 2
                ;;
          --b)  case "$2" in
                   '')  echo "b with no argument" ;;
                    *)  echo "b: $2" ;;
                esac
                shift 2
                ;;
           --)  shift; break ;;
            *)  printf "> %q\n" "$@"
                die 'getopt internal error: $*' ;;
        esac
    done
    
    echo "Remaining arguments:"
    for ((i=1; i<=$#; i++)); do
        echo "$i: ${!i}"
    done
    

    Successfully invoking the program with --P:

    $ ./myscript.sh --P -mt foo bar
    P with no argument
    m
    t
    Remaining arguments:
    1: foo
    2: bar
    
    $ ./myscript.sh --P=arg -mt foo bar
    P: arg
    m
    t
    Remaining arguments:
    1: foo
    2: bar
    

    This does impose higher overhead on your users, because -P (with one dash) is invalid, and the argument must be given with =

    $ ./myscript.sh --P arg -mt foo bar
    P with no argument
    m
    t
    Remaining arguments:
    1: arg
    2: foo
    3: bar
    
    $ ./myscript.sh --Parg mt foo bar
    myscript.sh: unrecognized option `--Parg'
    Problem parsing options
    
    $ ./myscript.sh -P -mt foo bar
    myscript.sh: invalid option -- P
    Problem parsing options
    
    $ ./myscript.sh -P=arg -mt foo bar
    myscript.sh: invalid option -- P
    myscript.sh: invalid option -- =
    myscript.sh: invalid option -- a
    myscript.sh: invalid option -- r
    myscript.sh: invalid option -- g
    Problem parsing options