bash case && extra OPTARG

4

Eu encontrei um cenário interessante na noite passada e, até agora, meu Google Foo não conseguiu encontrar uma solução. Eu tenho um script que suporta vários argumentos. Um usuário (maldito aqueles usuários) não especificou um argumento para uma opção e os resultados foram ... inesperados.

O código:

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

Especificar todos os argumentos corretamente resulta em:

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

Esquecendo os resultados de '-s' em:

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

Resultados semelhantes para pular outros argumentos com opções. Parece que o 'caso' perde a sua mente quando é apresentado a um OPTARG que não tem um OPT ...

Estou com uma perda de como entender isso.

    
por dkoleary 22.06.2017 / 15:00

2 respostas

4

Eu usaria getopt em vez de 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

Observe que quando você está pesquisando getopts vs. getopt , você encontrará muitas pessoas reclamando sobre getopt . Tanto quanto eu posso dizer, isso é sempre sobre uma versão mais antiga do getopt , que na verdade foi muito bugs. Minha experiência é que getopt tem mais opções e também é mais robusto que getopts .

Para verificar se você tem a versão getopt aprimorada, é possível executar

getopt -T
echo $?

Se a saída for 4 , você terá a versão aprimorada.

    
por 22.06.2017 / 15:19
2

A convenção tradicional do Unix é que uma linha de comando tem opções (incluindo argumentos de opções), então argumentos não-opcionais do comando. Em mwctl -D -a weblogic -c start admin -e trn -r , as opções são -D (sem argumento), -a (argumento: weblogic ) e -c (argumento: start ). A próxima palavra é admin , o que não é uma opção, então as opções acabaram. Os argumentos de não opção são, portanto, admin , -e , trn e -r . O getopt incorporado implementa essa convenção.

A convenção GNU é que um argumento que começa com - em qualquer lugar na linha de comando é uma opção, a menos que haja um argumento -- antes dele, com a advertência de que argumentos de opções não conte. Sob a convenção GNU, em mwctl -D -a weblogic -c start admin -e trn -r , há uma opção -D , uma opção -a com o argumento weblogic , uma opção -c com o argumento start , um argumento não opcional admin , uma opção -e com o argumento trn e uma opção -r .

case não "perde a cabeça", seu problema não tem nada a ver com case . Seu código está faltando a extração de argumentos não-opção:

case $arg in …
esac
shift $((OPTIND - 1))
echo "There are $# non-option arguments, the first is $1"

Se o comando não suportar argumentos que não sejam de opção, você precisa declarar isso explicitamente.

…
shift $((OPTIND - 1))
if [ $# -ne 0 ]; then
  echo >&2 "Extraneous argument: $1"
  exit 3
fi
    
por 23.06.2017 / 04:38