Bash case && extra OPTARG

bashgetoptsshell

I ran into an interesting scenario last night and, so far, my google foo has been unable to find a work around. I have a script that supports a number of arguments. A user (damn those users) didn't specify an argument for an option and the results were … unexpected.

The code:

while getopts "a:c:d:De:rs:" arg
do
  case ${arg} in
    a)  app=${OPTARG} ;;
    c)  cmd=${OPTARG} ;;
    d)  domain=${OPTARG} ;;
    D)  Debug=1 ;;
    e)  env=${OPTARG} ;;
    r)  Doit=1 ;;
    s)  subapp=${OPTARG} ;;
    *)  echo "You are DISCREPANT!!";;
    # *)  usage "Invalid argument ${arg}::${OPTARG}" ;;
  esac
done

if [ ${Debug} -gt 0 ]
then
  echo "Env:    ${env}"
  echo "App:    ${app}"
  echo "Subapp: ${subapp}"
  echo "Cmd:    ${cmd}"
  echo "Doit:   ${Doit}"
  echo "Debug:  ${Debug}"
  exit 1
fi

Specifying all the args correctly results in:

$ ./mwctl -a weblogic -c start -s admin -e trn -r -D
Env:    trn
App:    weblogic
Subapp: admin
Cmd:    start
Doit:   1
Debug:  1

Forgetting the '-s' results in:

$ ./mwctl -D -a weblogic -c start admin -e trn -r
Env:
App:    weblogic
Subapp:
Cmd:    start
Doit:   0
Debug:  1

Similar results for skipping other args with options. It seems that 'case' loses its mind when presented with an OPTARG that doesn't have an OPT…

I'm at a bit of a loss as to how to catch this.

Best Answer

I would use getopt instead of getopts:

#!/usr/bin/env bash

OPT=$(getopt \
    --options a:c:d:De:rs: \
    --name "$0" \
    -- "$@"
)

if [ $? -ne 0 ]; then
    echo You are doing it wrong!
    exit 1
fi

eval set -- "${OPT}"

while true; do
    case "$1" in
        -a)  app=${2}; shift 2;;
        -c)  cmd=${2}; shift 2;;
        -d)  domain=${2}; shift 2;;
        -D)  Debug=1; shift;;
        -e)  env=${2}; shift 2;;
        -r)  Doit=1; shift;;
        -s)  subapp=${2}; shift 2;;
        --)  break;;
    esac
done

echo "Env:    ${env}"
echo "App:    ${app}"
echo "Subapp: ${subapp}"
echo "Cmd:    ${cmd}"
echo "Doit:   ${Doit}"
echo "Debug:  ${Debug}"

$ ./mwctl -a weblogic -c start -s admin -e trn -r -D
> Env:    trn
> App:    weblogic
> Subapp: admin
> Cmd:    start
> Doit:   1
> Debug:  1

$ ./mwctl -D -a weblogic -c start admin -e trn -r   
> Env:    trn
> App:    weblogic
> Subapp: 
> Cmd:    start
> Doit:   1
> Debug:  1

Note that when you are googling getopts vs. getopt, you will find many people complaining about getopt. As far as I can tell, this is always about an older version of getopt, which indeed was very buggy. My experience is, that getopt has more options and is also more robust than getopts.

To check if you have the enhanced getopt version, you can run

getopt -T
echo $?

If the output is 4, you have the enhanced version.

Related Question