0

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?

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
  • 4
    `-w42l` is working exactly the way it's supposed to. Your expectation that it would be like `-w 42 -l` is wrong -- I've never seen a program where that expectation would be met; it's not the standard way to do things. – Charles Duffy Dec 19 '21 at 17:05
  • 1
    See the "Utility Syntax Guidelines" section at the end of https://pubs.opengroup.org/onlinepubs/9699919799.2016edition/basedefs/V1_chap12.html describing how programs are supposed to parse command-line arguments. In that context, `42` is what's called an "option argument". – Charles Duffy Dec 19 '21 at 17:06
  • Tangentially see also https://stackoverflow.com/questions/673055/correct-bash-and-shell-script-variable-capitalization – tripleee Dec 19 '21 at 17:08
  • Ah! Okay, if your college is forcing it that's different. Do you really need to use `getopts`, or can you implement your own parser? – Charles Duffy Dec 19 '21 at 17:11
  • Why isn't `-w42l` the same as `-w 42l`? What should happen on `-wanything_l_another_l_thing`? – KamilCuk Dec 19 '21 at 17:22
  • 1
    @Mr_BananaPants, ...btw, you might want to read [BashFAQ #35](http://mywiki.wooledge.org/BashFAQ/035) for a general introduction to option-parsing in bash. As it says, getopts covers the simple cases, but it's often inadequate for more complex needs. – Charles Duffy Dec 19 '21 at 17:40
  • @KamilCuk I believe the OP expects that `-w42l` is similar to `-w 42 -l` – kvantour Dec 19 '21 at 21:37

1 Answers1

1

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

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441