it-swarm-pt.tech

Como faço para analisar os argumentos da linha de comando no Bash?

Diga, eu tenho um script que é chamado com esta linha:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

ou este aqui:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Qual é a maneira aceita de analisar isso de forma que, em cada caso (ou em alguma combinação dos dois) $v, $f e $d, todos serão definidos como true e $outFile serão iguais a /fizz/someOtherFile?

1521
Lawrence Johnston

Método # 1: Usando o bash sem getopt [s]

Duas formas comuns de passar argumentos de par de valor-chave são:

Bash Space-Separated (por exemplo, --option argument) (sem getopt [s])

Uso ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
echo DEFAULT         = "${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi

Bash Equals-Separated (por exemplo, --option=argument) (sem getopt [s])

Uso ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/bin/bash

for i in "[email protected]"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

Para entender melhor ${i#*=} procure por "Substring Removal" em este guia . É funcionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` que chama um subprocesso desnecessário ou `echo "$i" | sed 's/[^=]*=//'` que chama dois subprocessos desnecessários. 

Método # 2: Usando o bash com getopt [s]

de: http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt (1) limitações (versões getopt mais antigas, relativamente recentes): 

  • não pode manipular argumentos que são sequências vazias
  • não pode manipular argumentos com espaço em branco incorporado

Versões mais recentes do getopt não possuem essas limitações.

Além disso, o shell POSIX (e outros) oferecem getopts que não tem essas limitações. Aqui está um exemplo simplista de getopts:

#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the Shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: [email protected]"

# End of file

As vantagens de getopts são:

  1. É mais portátil e funciona em outras shells como dash
  2. Ele pode lidar com várias opções únicas como -vf filename da maneira típica do Unix, automaticamente.

A desvantagem de getopts é que ele só pode manipular opções curtas (-h, not --help) sem código adicional.

Existe um tutorial getopts que explica o que todas as sintaxes e variáveis ​​significam. No bash, há também help getopts, que pode ser informativo.

2173
Bruno Bronosky

Nenhuma menção de resposta getopt aprimorada. E a resposta mais votada é enganosa: Ela ignora opções curtas -⁠vfd style (solicitadas pelo OP), opções após argumentos posicionais (também solicitadas pelo OP) e ignora erros de análise. Em vez de:

  • Use a getopt aprimorada do util-linux ou anteriormente GNU glibc.1
  • Ele funciona com getopt_long() a função C do GNU glibc.
  • Tem todos características distintivas úteis (as outras não as têm):
    • manipula espaços, citando caracteres e até binário em argumentos2
    • ele pode manipular as opções no final: script.sh -o outFile file1 file2 -v
    • permite =- style long options: script.sh --outfile=fileOut --infile fileIn
  • Já é tão antigo3 que nenhum sistema GNU está faltando (por exemplo, qualquer Linux tem isso).
  • Você pode testar sua existência com: getopt --test → return value 4.
  • Outros getopt ou getopts embutidos no shell __ são de uso limitado.

As seguintes chamadas

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

todos retornam

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

com a seguinte myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -use ! and PIPESTATUS to get exit code with errexit set
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "[email protected]"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 o getopt aprimorado está disponível na maioria dos "bash-systems", incluindo o Cygwin; no OS X, tente brew install gnu-getopt ou Sudo port install getopt
2 as convenções POSIX exec() não possuem uma maneira confiável de passar o binário NULL nos argumentos da linha de comando; esses bytes terminam prematuramente o argumento
3 primeira versão lançada em 1997 ou antes (eu só a rastreei até 1997)

419
Robert Siemer

de: digitalpeer.com com pequenas modificações

Uso myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "[email protected]"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Para entender melhor ${i#*=} procure por "Substring Removal" em este guia . É funcionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` que chama um subprocesso desnecessário ou `echo "$i" | sed 's/[^=]*=//'` que chama two subprocessos desnecessários.

114
guneysus

getopt()/getopts() é uma boa opção. Roubado de aqui :

O uso simples de "getopt" é mostrado neste mini-script:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

O que dissemos é que qualquer um de -a, -B, -c ou -d será permitido, mas que -c é seguido por um argumento (o "c:" diz isso).

Se chamamos isso de "g" e experimentamos:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

Começamos com dois argumentos, e "Getopt" separa as opções e Coloca cada uma em seu próprio argumento. Ele também Adicionou "-".

102
Matt J

Correndo o risco de adicionar outro exemplo para ignorar, aqui está o meu esquema.

  • lida com -n arg e --name=arg
  • permite argumentos no final
  • mostra erros sãos se alguma coisa está incorreta
  • compatível, não usa bashisms
  • legível, não requer a manutenção do estado em um loop

Espero que seja útil para alguém.

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done
84
bronson

Maneira mais sucinta

script.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do case $1 in
  -d|--deploy) deploy="$2"; shift;;
  -u|--uglify) uglify=1;;
  *) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

Uso:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
78
Inanc Gumus

Estou cerca de 4 anos atrasado para esta questão, mas quero devolver. Eu usei as respostas anteriores como ponto de partida para arrumar minha antiga análise de parâmetros ad hoc. Eu então refatorei o seguinte código de modelo. Ele lida com parâmetros longos e curtos, usando argumentos = ou espaços separados, bem como vários parâmetros curtos agrupados. Por fim, reinsira novamente os argumentos não-param nas variáveis ​​$ 1, $ 2 .. Espero que seja útil.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 [email protected] ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable Shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the Shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
39
Shane Day

Minha resposta é em grande parte baseada em a resposta de Bruno Bronosky , mas eu meio que triturei suas duas implementações bash puras em uma que eu uso com bastante frequência.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Isso permite que você tenha opções/valores separados por espaço, bem como valores definidos iguais.

Então você pode rodar seu script usando:

./myscript --foo -b -o /fizz/file.txt

assim como:

./myscript -f --bar -o=/fizz/file.txt

e ambos devem ter o mesmo resultado final.

PROS:

  • Permite tanto o valor de -arg = como o valor de -arg

  • Funciona com qualquer nome de arg que você possa usar no bash

    • Significado -a ou -arg ou --arg ou -a-r-g ou qualquer outra coisa
  • Festa pura. Não há necessidade de aprender/usar getopt ou getopts

CONS:

  • Não é possível combinar args

    • Significado não -abc. Você deve fazer -a -b -c

Estes são os únicos prós e contras que posso pensar em cima da minha cabeça

25
Ponyboy47

Eu encontrei o assunto para escrever análise portátil em scripts tão frustrante que eu escrevi Argbash - um gerador de código FOSS que pode gerar o código de análise de argumentos para o seu script mais ele tem alguns recursos interessantes:

https://argbash.io

24
bubla

Expandindo a excelente resposta do @guneysus, aqui está um Tweak que permite ao usuário usar a sintaxe que preferir, por exemplo

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

Isso quer dizer que os iguais podem ser substituídos por espaços em branco. 

Essa "interpretação difusa" pode não ser do seu agrado, mas se você estiver criando scripts que sejam intercambiáveis ​​com outros utilitários (como é o caso do meu, que deve funcionar com o ffmpeg), a flexibilidade é útil.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "[email protected]"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done
13
unsynchronized

Eu acho que este é simples o suficiente para usar:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Exemplo de invocação:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
13
Alek

Eu te dou a função parse_params que irá analisar params da linha de comando.

  1. É uma solução Bash pura, sem utilitários adicionais.
  2. Não polui o escopo global. 
  3. Sem esforço, você retorna simples para usar variáveis, que você poderia construir uma nova lógica.
  4. Quantidade de traços antes de params não importa (--all é igual a -all é igual a all=all)

O script abaixo é uma demonstração de trabalho de copiar e colar. Veja a função show_use para entender como usar parse_params.

Limitações: 

  1. Não suporta parâmetros delimitados por espaço (-d 1)
  2. Os nomes dos parametros perdem traços para que --any-param e -anyparam sejam equivalentes
  3. eval $(parse_params "[email protected]") deve ser usado dentro do bash function (não funcionará no escopo global)

#!/bin/bash

# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion
        _escaped=${1/\*/\'\"*\"\'}
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key=\"$_val\";"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        Elif [[ "$1" =~ ^\-. ]]; then
            # remove dashes
            local _key=${1//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "[email protected]")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
8
Oleksii Chekulaiev

o getopts funciona muito bem se o # 1 estiver instalado e o # 2 que você pretende rodá-lo na mesma plataforma. O OSX e o Linux (por exemplo) se comportam de maneira diferente a esse respeito.

Aqui está uma solução (não getopts) que suporta sinalizadores de igualdade, não-iguais e booleanos. Por exemplo, você poderia executar seu script desta maneira:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("[email protected]")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        Elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        Elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done
8
vangorra

EasyOptions não requer qualquer análise:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi
7
Renato Silva

Isto é como eu faço em uma função para evitar que os getopts sejam executados ao mesmo tempo em algum lugar mais alto na pilha:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local Host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         Host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}
6
akostadinov

Gostaria de oferecer minha versão de análise de opção, que permite o seguinte:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

Também permite isso (pode ser indesejado):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Você tem que decidir antes de usar se = é para ser usado em uma opção ou não. Isso é para manter o código limpo (ish).

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done
4
galmok

Note que getopt(1) foi um erro de vida curta da AT & T.

o getopt foi criado em 1984, mas já foi enterrado em 1986 porque não era realmente utilizável.

Uma prova para o fato de que getopt está muito desatualizada é que a página man getopt(1) ainda menciona "$*" em vez de "[email protected]", que foi adicionada ao Bourne Shell em 1986 junto com o getopts(1) Shell embutido para lidar com argumentos dentro de espaços.

BTW: se você estiver interessado em analisar opções longas em scripts Shell, pode ser interessante saber que a implementação getopt(3) da libc (Solaris) e ksh93 adicionou uma implementação de opção longa uniforme que suporta opções longas como aliases para opções curtas. Isso faz com que ksh93 e Bourne Shell implementem uma interface uniforme para opções longas via getopts.

Um exemplo de opções longas tiradas da página do manual do Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

mostra por quanto tempo os aliases de opções podem ser usados ​​no Bourne Shell e no ksh93.

Veja a man page de uma recente Bourne Shell:

http://schillix.sourceforge.net/man/man1/bosh.1.html

e a página man do getopt (3) do OpenSolaris:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

e, por último, a página man do getopt (1) para verificar o $ * desatualizado:

http://schillix.sourceforge.net/man/man1/getopt.1.html

4
schily

Suponha que nós criamos um script Shell chamado test_args.sh como segue

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

Depois de executar o seguinte comando:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

A saída seria:

year=2017 month=12 day=22 flag=true
3
John

Misturando argumentos posicionais e baseados em sinalização

--param = arg (é igual a delimitado)

Misturando bandeiras livremente entre argumentos posicionais:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

pode ser realizado com uma abordagem bastante concisa:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--param arg (delimitado por espaço)

É normalmente mais claro não misturar os estilos --flag=value e --flag value.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Isso é um pouco arriscado para ler, mas ainda é válido

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Fonte

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2
2
Mark Fox

Eu escrevi um ajudante para escrever uma ferramenta de bash

home do projeto: https://gitlab.mbedsys.org/mbedsys/bashopts

exemplo:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "[email protected]"

# Process argument
bashopts_process_args

vai dar ajuda:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

apreciar :)

2
Emeric Verschuur

Aqui está a minha abordagem - usando regexp.

  • sem getopts
  • ele manipula o bloco de parâmetros curtos -qwerty
  • ele manipula parâmetros curtos -q -w -e
  • ele lida com opções longas --qwerty
  • você pode passar o atributo para a opção curta ou longa (se você estiver usando o bloco de opções curtas, o atributo é anexado à última opção)
  • você pode usar espaços ou = para fornecer atributos, mas atribui correspondências até encontrar hífen + espaço "delimitador", então em --q=qwe tyqwe ty é um atributo
  • ele lida com mix de todos acima, então -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute é válido

roteiro:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
[email protected]

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    Elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done
2
a_z

Solução que preserva argumentos não manipulados. Demos incluídos.

Aqui está a minha solução. É MUITO flexível e diferente de outros, não deve exigir pacotes externos e manipula os argumentos restantes com clareza.

O uso é: ./myscript -flag flagvariable -otherflag flagvar2

Tudo o que você precisa fazer é editar a linha validflags. Ele preends um hífen e pesquisa todos os argumentos. Em seguida, define o próximo argumento como o nome do sinalizador, por ex.

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

O código principal (versão curta, detalhada com exemplos mais abaixo, também uma versão com erro de saída):

#!/usr/bin/env bash
#Shebang.io
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

A versão detalhada com demonstrações de eco incorporadas:

#!/usr/bin/env bash
#Shebang.io
rate=30
time=30
number=30
echo "all args
[email protected]"
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
#   argval=$(echo [email protected] | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
[email protected]"
shift $#
echo "post final clear args:
[email protected]"
set -- $leftovers
echo "all post set args:
[email protected]"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

Final um, este erro se um argumento inválido for passado.

#!/usr/bin/env bash
#Shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in [email protected]
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

Prós: O que faz, lida muito bem. Ele preserva argumentos não utilizados que muitas das outras soluções não contêm. Também permite que variáveis ​​sejam chamadas sem serem definidas manualmente no script. Também permite o preenchimento prévio de variáveis ​​se nenhum argumento correspondente for fornecido. (Veja o exemplo detalhado).

Contras: Não é possível analisar uma única cadeia de caracteres complexa, por ex. -xcvf processaria como um único argumento. Você poderia facilmente escrever código adicional no meu que adiciona essa funcionalidade embora. 

2
Noah

Outra solução sem getopt [s], POSIX, antigo estilo Unix

Similar a a solução que Bruno Bronosky postou isto aqui é um sem o uso de getopt(s).

A principal característica diferenciadora da minha solução é que ela permite que as opções sejam concatenadas juntas, assim como tar -xzf foo.tar.gz é igual a tar -x -z -f foo.tar.gz. E assim como em tar, ps etc., o hífen principal é opcional para um bloco de opções curtas (mas isso pode ser alterado facilmente). Opções longas também são suportadas (mas quando um bloco começa com um, então dois hífens principais são necessários).

Código com opções de exemplo

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  Elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Para o uso do exemplo, consulte os exemplos abaixo.

Posição das opções com argumentos

Para o que vale a pena, as opções com argumentos não são as últimas (apenas as opções longas precisam ser). Então, enquanto em tar (pelo menos em algumas implementações) as opções f precisam ser as últimas porque o nome do arquivo é o seguinte (tar xzf bar.tar.gz funciona mas tar xfz bar.tar.gz não) isso não é o caso aqui (veja os exemplos mais recentes).

Múltiplas opções com argumentos

Como outro bônus, os parâmetros da opção são consumidos na ordem das opções pelos parâmetros com as opções necessárias. Basta olhar para a saída do meu script aqui com a linha de comando abc X Y Z (ou -abc X Y Z):

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Longas opções concatenadas também

Além disso, você também pode ter opções longas no bloco de opções, uma vez que elas ocorrem por último no bloco. Portanto, as seguintes linhas de comando são todas equivalentes (incluindo a ordem na qual as opções e seus argumentos estão sendo processados):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

Tudo isso leva a:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

Não nesta solução

Argumentos opcionais

Opções com argumentos opcionais devem ser possíveis com um pouco de trabalho, por ex. olhando para a frente se existe um bloco sem um hífen; o usuário precisaria colocar um hífen na frente de cada bloco seguindo um bloco com um parâmetro com um parâmetro opcional. Talvez isso seja muito complicado para se comunicar com o usuário, então é melhor apenas exigir um hífen líder nesse caso.

As coisas ficam ainda mais complicadas com vários parâmetros possíveis. Eu recomendaria contra fazer as opções tentando ser inteligente, determinando se o argumento pode ser para ele ou não (por exemplo, com uma opção apenas leva um número como um argumento opcional) porque isso pode quebrar no futuro.

Eu pessoalmente prefiro opções adicionais em vez de argumentos opcionais.

Argumentos de opção introduzidos com um sinal de igual

Assim como com argumentos opcionais eu não sou um fã disso (BTW, existe um tópico para discutir os prós/contras de diferentes estilos de parâmetro?), Mas se você quiser isso, você provavelmente poderia implementá-lo como feito em http: //mywiki.wooledge.org/BashFAQ/035#Manual_loop com uma instrução de caso --long-with-arg=?* e, em seguida, removendo o sinal de igual (este é BTW o site que diz que fazer a concatenação de parâmetro é possível com algum esforço, mas "deixou [como] um exercício para o leitor "que me fez levá-los em sua Palavra, mas eu comecei do zero).

Outras notas

Compatível com POSIX, funciona mesmo em configurações antigas do Busybox com as quais tive de lidar (com, por exemplo, cut, head e getopts missing).

1
phk

Isso também pode ser útil saber, você pode definir um valor e se alguém fornecer entrada, substitua o padrão com esse valor. 

myscript.sh -f ./serverlist.txt ou apenas ./myscript.sh (e recebe os padrões)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"
1
Mike Q

Aqui está minha solução melhorada da resposta de Bruno Bronosky usando matrizes variáveis.

ele permite que você misture a posição dos parâmetros e forneça um array de parâmetros preservando o pedido sem as opções

#!/bin/bash

echo [email protected]

PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

Irá produzir por exemplo:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile
1
Masadow

Expandindo a resposta de @bruno-bronosky, adicionei um "pré-processador" para lidar com alguma formatação comum:

  • Expande --longopt=val em --longopt val
  • Expande -xyz em -x -y -z
  • Suporta -- para indicar o fim dos sinalizadores
  • Mostra um erro para opções inesperadas
  • Interruptor de opções compacto e fácil de ler
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"
1
jchook

Este exemplo mostra como usar getopt e eval e HEREDOC e shift para manipular parâmetros curtos e longos com e sem um valor obrigatório a seguir. Além disso, a instrução switch/case é concisa e fácil de seguir.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, don't change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

As linhas mais significativas do script acima são estas:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Curto, ao ponto, legível e manipula praticamente tudo (IMHO).

Espero que ajude alguém.

1
phyatt

Use o módulo "argumentos" de bash-modules

Exemplo:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"
1
Volodymyr M. Lisivka

Eu quero enviar meu projeto: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "[email protected]"

Simples assim. O ambiente será preenchido com variáveis ​​com o mesmo nome que os argumentos

1
Thanh Trung

A melhor resposta a esta questão pareceu um pouco problemática quando a experimentei - eis a minha solução que descobri ser mais robusta:

boolean_arg=""
arg_with_value=""

while [[ $# -gt 0 ]]
do
key="$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value="$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg="$1"
        shift
        ;;
        2)
        second_normal_arg="$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi
1
Daniel Bigham

Simples e fácil de modificar, os parâmetros podem estar em qualquer ordem. isso pode ser modificado para aceitar parâmetros de qualquer forma (-a, --a, a, etc).

for arg in "[email protected]"
do
   key=$(echo $arg | cut -f1 -d=)`
   value=$(echo $arg | cut -f2 -d=)`
   case "$key" in
        name|-name)      read_name=$value;;
        id|-id)          read_id=$value;;
        *)               echo "I dont know what to do with this"
   ease
done
0
terijo001