Search code examples
bashshellgetopts

bash getopts uses option/flag as argument


I'm writing a bash shell script that can use the flags -l -u and -w [argument]

I have the following (simplified) code:

while getopts ":ulw:" arg; do
    case "$arg" in
    u)
        U=1
        ;;
    l)
        L=1
        ;;

    w)
        W=1
        VALUE="${OPTARG}"
        ;;

    esac
done

when I run my script with -w42 -l it works like it should. If I use -lw42 it also works but when I use -w42l, it thinks 42l is the argument (instead of just 42) and it makes the VALUE variable = 42l and ignores the -l option.

How can I make the script work for both -w42 -l, -lw42 and -w42l?


Solution

  • On Standards-Compliance

    What you are trying is not supposed to work in the first place.

    POSIX utility syntax guideline #5 states:

    One or more options without option-arguments, followed by at most one option that takes an option-argument, should be accepted when grouped behind one '-' delimiter.

    So, the option taking an option-argument (-w in this case) is only allowed to be the last one in a group started by a single -.


    On Making It Work Anyhow

    If you can't deal with standard-compliant behavior, you can't use getopts, so you need to write your own logic. One way to do that might look like the following:

    #!/usr/bin/env bash
    #              ^^^^- note bash, not sh; the below code uses non-POSIX extensions
    
    while (( $# )) && [[ $1 = -* ]]; do
      arg=${1#-}; shift
      while [[ $arg ]]; do
        case $arg in
          l*) flag_l=1; arg=${arg#l};;
          u*) flag_u=1; arg=${arg#u};;
          w*)
            flag_w=1
            rest=${arg#w}
            if [[ -z $rest ]]; then
              arg=$1; shift; rest=$arg
            fi
            if [[ $rest =~ ^([[:digit:]]+)(.*) ]]; then
              w_value=${BASH_REMATCH[1]}
              arg=${BASH_REMATCH[2]}
            else
              echo "ERROR: -w not followed with a number" >&2
              exit 1
            fi
            ;;
          *) echo "Unrecognized flag: $arg" >&2; exit 1;;
        esac
      done
    done
    
    echo "After parsing:"
    echo "flag_w = ${flag_w:-0}"
    echo "flag_l = ${flag_l:-0}"
    echo "flag_u = ${flag_u:-0}"
    echo "w_value = ${w_value:-0}"
    

    See this running in the online interpreter at https://ideone.com/eDrlHd