optarg - shell script getopts




Usando getopts no bash shell script para obter opções de linha de comando longas e curtas (20)

Eu gostaria de ter formas longas e curtas de opções de linha de comando invocadas usando meu script de shell.

Eu sei que getopts podem ser usados, mas como em Perl, eu não fui capaz de fazer o mesmo com shell.

Alguma idéia de como isso pode ser feito, para que eu possa usar opções como:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

Acima, ambos os comandos significam a mesma coisa para o meu shell, mas usando getopts , eu não consegui implementá-los?


Usando getopts com opções e argumentos curtos / longos

Funciona com todas as combinações, por exemplo:

  • foobar -f --bar
  • foobar - foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --argumentos = longhorn
  • foobar -fA "texto baixinho" -B --argumentos = "texto longhorn"
  • bash foobar -F - barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Algumas declarações para este exemplo

Options=[email protected]
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

Como a função de uso ficaria

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops com getops longos / curtos, bem como argumentos longos

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Saída

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

Combinando o acima em um script coeso

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=[email protected]
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

A função getopts internos do Bash pode ser usada para analisar opções longas, colocando um caractere de traço seguido por dois pontos no optspec:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

Depois de copiar para o nome do arquivo executável = getopts_test.sh no diretório de trabalho atual , pode-se produzir uma saída como

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

Obviamente, o getopts não executa a verificação do OPTERR nem a análise do argumento de opção para as opções longas. O fragmento de script acima mostra como isso pode ser feito manualmente. O princípio básico também funciona no shell do Debian Almquist ("traço"). Observe o caso especial:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

Note que, como GreyCat de em http://mywiki.wooledge.org/BashFAQ aponta, este truque explora um comportamento não-padrão do shell que permite a opção-argumento (ou seja, o nome do arquivo em "-f filename") para ser concatenado com a opção (como em "-ffilename"). O padrão POSIX diz que deve haver um espaço entre eles, que no caso de "- longoption" terminaria a análise de opções e transformaria todas as longoptions em argumentos não-opcionais.


Dê uma olhada no shFlags que é uma biblioteca de shell portátil (significando: sh, bash, dash, ksh, zsh no Linux, Solaris, etc.).

Isso torna a adição de novos sinalizadores tão simples quanto adicionar uma linha ao seu script e fornece uma função de uso gerado automaticamente.

Aqui está um simples Hello, world! usando shFlag :

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "[email protected]" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

Para SOs que possuem o getopt aprimorado que suporta opções longas (por exemplo, Linux), você pode fazer:

$ ./hello_world.sh --name Kate
Hello, Kate!

Para o resto, você deve usar a opção curta:

$ ./hello_world.sh -n Kate
Hello, Kate!

Adicionar um novo sinalizador é tão simples quanto adicionar uma nova DEFINE_ call .


Eu meio que resolvi assim:

# A string with command options
options=[email protected]

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

Estou sendo burra ou algo assim? getopt e getopts são tão confusos.


O comando built-in getopts ainda é, AFAIK, limitado apenas às opções de caractere único.

Existe (ou costumava ser) um programa externo getopt que reorganizaria um conjunto de opções de modo que fosse mais fácil de analisar. Você pode adaptar esse design para lidar com opções longas também. Exemplo de uso:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "[email protected]")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

Você poderia usar um esquema semelhante com um comando getoptlong .

Note que a fraqueza fundamental com o programa getopt externo é a dificuldade de manipular argumentos com espaços neles e preservar esses espaços com precisão. É por isso que o getopts integrado é superior, embora limitado pelo fato de lidar apenas com opções de uma única letra.


Opções longas podem ser analisadas pelos getopts padrão embutidos como “argumentos” para a opção “”

Este é um shell POSIX portátil e nativo - não são necessários programas externos ou bashisms.

Este guia implementa opções longas como argumentos para a opção - , então --alpha é visto por getopts como - com argumento alpha e --bravo=foo é visto como - com argumento bravo=foo . O verdadeiro argumento pode ser obtido com uma substituição simples: ${OPTARG#*=} .

Neste exemplo, -b (e sua forma longa, --bravo ) tem uma opção obrigatória (observe a reconstrução manual de impor isso para a forma longa). Opções não-booleanas para argumentos longos vêm depois de sinais de igual, por exemplo --bravo=foo (delimitadores de espaço para opções longas seriam difíceis de implementar).

Como isso usa getopts , essa solução suporta o uso como cmd -ac --bravo=foo -d FILE (que combina opções -a e c e intercala opções longas com opções padrão) enquanto a maioria das outras respostas aqui se esforçam ou não conseguem fazer naquela.

while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo "No arg for --$OPTARG option" >&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
           '' )        break ;; # "--" terminates argument processing
           * )         echo "Illegal option --$OPTARG" >&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from [email protected] list

Quando o argumento é um traço ( - ), ele tem mais dois componentes: o nome do sinalizador e (opcionalmente) seu argumento. Eu delimito a forma padrão de qualquer comando, com o primeiro sinal de igual ( = ). $LONG_OPTARG é, portanto, apenas o conteúdo de $OPTARG sem o nome do sinalizador ou sinal de igual.

O case interno implementa as opções longas manualmente, por isso precisa de algumas tarefas domésticas:

  • bravo=? matches --bravo=foo mas não --bravo= (nota: o case para após o primeiro jogo)
  • bravo* segue, observando o argumento obrigatório ausente em --bravo e --bravo=
  • alpha* | charlie* alpha* | charlie* pega argumentos dados às opções que não os suportam
  • '' está presente para suportar opções que começam com traços
  • * pega todas as outras opções longas e recria o erro lançado por getopts para uma opção inválida

Você não precisa necessariamente de todos esses itens de limpeza. Por exemplo, talvez você queira que --bravo tenha um argumento opcional (o qual -b não suporta devido a uma limitação em getopts ). Simplesmente remova o =? e o caso de falha relacionado e, em seguida, chame ${ARG_B:=$DEFAULT_ARG_B} na primeira vez que você usar $ARG_B .


EasyOptions lida com opções curtas e longas:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi

getopt e getopts são bestas diferentes, e as pessoas parecem ter um pouco de mal-entendido sobre o que fazem. getopts é um comando bash para bash processamento de opções de linha de comando em um loop e atribuir cada opção e valor encontrados às variáveis ​​internas, para que você possa processá-las ainda mais. getopt , no entanto, é um programa utilitário externo e, na verdade, não processa suas opções para você da mesma forma que, por exemplo, o getopts bash, o módulo Perl Getopt ou os módulos argparse / argparse do Python. Tudo o que o getopt faz é canonizar as opções que são passadas - isto é, convertê-las para um formato mais padronizado, para que seja mais fácil para um script de shell processá-las. Por exemplo, um aplicativo de getopt pode converter o seguinte:

myscript -ab infile.txt -ooutfile.txt

nisso:

myscript -a -b -o outfile.txt infile.txt

Você precisa fazer o processamento real sozinho. Você não precisa usar getopt se fizer várias restrições na maneira de especificar opções:

  • apenas coloque uma opção por argumento;
  • todas as opções vão antes de qualquer parâmetro posicional (ou seja, argumentos não-opcionais);
  • para opções com valores (ex. -o acima), o valor tem que ir como um argumento separado (depois de um espaço).

Por que usar getopt vez de getopts ? A razão básica é que somente o GNU getopt lhe dá suporte para opções de linha de comando com nomes longos. 1 (GNU getopt é o padrão no Linux. Mac OS X e FreeBSD vêm com um getopt básico e não muito útil, mas a versão GNU pode ser instalada; veja abaixo.)

Por exemplo, aqui está um exemplo do uso do GNU getopt , a partir de um script meu chamado javawrap :

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "[email protected]"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Isso permite especificar opções como --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt" ou similar. O efeito da chamada para getopt é canonizar as opções para --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt" para que você possa processar mais facilmente eles. A cotação em torno de "$1" e "$2" é importante, pois garante que argumentos com espaços neles sejam manuseados corretamente.

Se você excluir as primeiras 9 linhas (tudo através da linha de eval set ), o código ainda funcionará ! No entanto, o seu código será muito mais exigente em que tipos de opções ele aceita: Em particular, você terá que especificar todas as opções no formulário "canônico" descrito acima. Com o uso de getopt , no entanto, você pode agrupar opções de uma única letra, usar formas não-ambíguas mais curtas de opções longas, usar o estilo --file foo.txt ou --file=foo.txt , use o --file=foo.txt -m 4096 ou -m4096 , misturar opções e não opções em qualquer ordem, etc. getopt também gera uma mensagem de erro se opções não reconhecidas ou ambíguas forem encontradas.

NOTA : Na verdade, existem duas versões totalmente diferentes de getopt , basic getopt e GNU getopt , com diferentes recursos e diferentes convenções de chamada. 2 O getopt básico está bastante quebrado: ele não apenas não manipula opções longas, mas também não pode manipular espaços embutidos dentro de argumentos ou argumentos vazios, enquanto o getopts faz isso direito. O código acima não funcionará no getopt básico. O GNU getopt é instalado por padrão no Linux, mas no Mac OS X e no FreeBSD ele precisa ser instalado separadamente. No Mac OS X, instale o MacPorts ( http://www.macports.org ) e faça sudo port install getopt para instalar o GNU getopt (geralmente em /opt/local/bin ), e certifique-se de que /opt/local/bin está no caminho do shell antes de /usr/bin . No FreeBSD, instale misc/getopt .

Um guia rápido para modificar o código de exemplo do seu próprio programa: Das primeiras poucas linhas, tudo é "boilerplate" que deve permanecer o mesmo, exceto a linha que chama getopt . Você deve alterar o nome do programa após -n , especificar opções curtas após -o e opções longas após --long . Coloque dois pontos após as opções que levam um valor.

Finalmente, se você ver o código que acabou de set vez de eval set , ele foi escrito para o BSD getopt . Você deve alterá-lo para usar o estilo do eval set , que funciona bem com ambas as versões do getopt , enquanto o set simples não funciona com o GNU getopt .

1 Na verdade, getopts no ksh93 suporta opções de longo nome, mas este shell não é usado com bash frequência quanto o bash . Em zsh , use zparseopts para obter essa funcionalidade.

2 Tecnicamente, "GNU getopt " é um equívoco; esta versão foi escrita para o Linux ao invés do projeto GNU. No entanto, segue todas as convenções GNU, e o termo "GNU getopt " é comumente usado (por exemplo, no FreeBSD).


Eu só escrevo scripts de shell de vez em quando e deixo de praticar, então qualquer feedback é apreciado.

Usando a estratégia proposta pelo @Arvid Requate, notamos alguns erros do usuário. Um usuário que esquecer de incluir um valor acidentalmente terá o nome da próxima opção tratado como um valor:

./getopts_test.sh --loglevel= --toc=TRUE

fará com que o valor de "loglevel" seja visto como "--toc = TRUE". Isso pode ser evitado.

Eu adaptei algumas idéias sobre a verificação de erro do usuário para CLI da http://mwiki.wooledge.org/BashFAQ/035 discussão da análise manual. Inseri a verificação de erros no tratamento de argumentos "-" e "-".

Então eu comecei a brincar com a sintaxe, então quaisquer erros aqui são estritamente culpa minha, não dos autores originais.

Minha abordagem ajuda os usuários que preferem entrar por muito tempo com ou sem o sinal de igual. Ou seja, deve ter a mesma resposta para "--loglevel 9" como "--loglevel = 9". No método - / space, não é possível saber com certeza se o usuário esquece um argumento, portanto, é necessária alguma adivinhação.

  1. se o usuário tiver o formato de sinal longo / igual (--opt =), então um espaço após = aciona um erro porque um argumento não foi fornecido.
  2. se o usuário tiver argumentos long / space (--opt), esse script causará uma falha se nenhum argumento for seguido (fim de comando) ou se o argumento começar com traço)

Caso você esteja começando, há uma diferença interessante entre os formatos "--opt = value" e "--opt value". Com o sinal de igual, o argumento da linha de comando é visto como "opt = value" e o trabalho a ser manipulado é a análise de strings, para separar no "=". Em contraste, com "--opt value", o nome do argumento é "opt" e temos o desafio de obter o próximo valor fornecido na linha de comando. É aí que @Arvid Requate usou $ {! OPTIND}, a referência indireta. Eu ainda não entendo isso, bem, em tudo, e os comentários no BashFAQ parecem alertar contra esse estilo ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, eu não acho comentários anteriores sobre a importância de OPTIND = $ (($ OPTIND + 1)) estão corretos. Eu quis dizer,Não vejo mal em omitir isso.

Na versão mais recente deste script, flag -v significa impressão de VERBOSE.

Salve em um arquivo chamado "cli-5.sh", torne executável, e qualquer um deles funcionará, ou falhará da maneira desejada

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

Aqui está um exemplo de saída da verificação de erros no usuário intpu

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

Você deve considerar ligar -v, porque ele imprime internamente de OPTIND e OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## https://.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

Inventando ainda outra versão da roda ...

Esta função é uma (esperançosamente) substituição da shell de bourne simples compatível com POSIX para o GNU getopt. Ele suporta opções curtas / longas que podem aceitar argumentos obrigatórios / opcionais / não, e a maneira como as opções são especificadas é quase idêntica ao GNU getopt, então a conversão é trivial.

É claro que este ainda é um pedaço considerável de código para ser incluído em um script, mas é cerca de metade das linhas da função de shell getopt_long bem conhecida, e pode ser preferível nos casos em que você deseja substituir os usos GNU getopt existentes.

Este é um código muito novo, então YMMV (e definitivamente por favor me avise se isso não é compatível com POSIX por algum motivo - portabilidade era a intenção desde o início, mas eu não tenho um ambiente de teste POSIX útil).

Código e exemplo de uso a seguir:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "[email protected]") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "[email protected]")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "[email protected]"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "[email protected]")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "[email protected]")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

Exemplo de uso:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "[email protected]")
opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

Se todas as suas opções longas tiverem caracteres iniciais únicos e correspondentes, como as opções curtas, assim, por exemplo

./slamm --chaos 23 --plenty test -quiet

É o mesmo que

./slamm -c 23 -p test -q

Você pode usar isso antes de getopts para reescrever $ args:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

Obrigado por mtvee pela inspiração ;-)


Talvez seja mais simples usar o ksh, apenas para a parte de getopts, se precisar de opções de linha de comando longas, pois isso pode ser feito com mais facilidade.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

Uma solução aprimorada:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift

getopts "poderia ser usado" para analisar opções longas, desde que você não espere que elas tenham argumentos ...

Veja como:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

Se você tentar usar OPTIND para obter um parâmetro para a opção longa, o getopts o tratará como o primeiro parâmetro posicional não opcional e parará de analisar quaisquer outros parâmetros. Nesse caso, será melhor manipulá-lo manualmente com uma declaração de caso simples.

Isso "sempre" funcionará:

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

Embora não seja tão flexível quanto o getopts e você tem que fazer muito do código de verificação de erros você mesmo dentro das instâncias do caso ...

Mas é uma opção.


Ainda não tenho representantes suficientes para comentar ou votar em sua solução, mas a resposta da minha esposa funcionou muito bem para mim. O único problema que eu encontrei foi que os argumentos acabaram envoltos em aspas simples (então eu tenho uma tira deles).

Eu também adicionei alguns exemplos de uso e texto de ajuda. Eu incluirei minha versão ligeiramente estendida aqui:

#!/bin/bash

# getopt example
# from: https://.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "[email protected]")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

Aqui você pode encontrar algumas abordagens diferentes para análise de opções complexas no bash: http://mywiki.wooledge.org/ComplexOptionParsing

Eu criei o seguinte, e eu acho que é bom, porque é um código mínimo e tanto as opções longas quanto as curtas funcionam. Uma opção longa também pode ter vários argumentos com essa abordagem.

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

Em ksh93, getoptssuporta nomes longos ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

Ou então os tutoriais que eu encontrei disseram. Experimente e veja.


Eu queria algo sem dependências externas, com suporte bash estrito (-u), e eu precisava trabalhar até mesmo nas versões mais antigas do bash. Isso lida com vários tipos de parâmetros:

  • bools curtos (-h)
  • opções curtas (-i "image.jpg")
  • bools longos (--help)
  • opções de iguais (--file = "filename.ext")
  • opções de espaço (--file "filename.ext")
  • bools concatenados (-hvm)

Basta inserir o seguinte no topo do seu script:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

E use assim:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*

O built-in getoptsnão pode fazer isso. Existe um programa getopt (1) externo que pode fazer isso, mas você só o obtém no Linux a partir do pacote util-linux . Ele vem com um script de exemplo getopt-parse.bash .

Existe também uma função getopts_longescrita como shell.


Th built-in OS X (BSD) getopt não suporta opções longas, mas a versão GNU faz: brew install gnu-getopt. Então, algo semelhante a: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.







getopts