Utiliser getopts dans le script shell bash pour obtenir des options en ligne de commande longues et courtes

je souhaite que des formes longues et courtes d'options de ligne de commande soient invoquées en utilisant mon script shell.

je sais que getopts peut être utilisé, mais comme en Perl, je n'ai pas été capable de faire la même chose avec shell.

toutes les idées sur la façon dont cela peut être fait, afin que je puisse utiliser des options comme:

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

dans ce qui précède, les deux commandes signifient la même chose pour mon shell, mais en utilisant getopts , Je n'ai pas été capable d'implémenter ces?

355
demandé sur Benjamin W. 2008-12-31 08:49:05

29 réponses

The bash getopts builtin ne supporte pas les noms d'options longues avec le préfixe à double tiret. Il ne supporte que les options à caractère unique.

il y a un outil shell getopt qui est un autre programme, pas un builtin de bash. L'implémentation GNU de getopt(3) (utilisé par la ligne de commande getopt(1) sous Linux) supporte l'analyse des options longues.

mais la mise en œuvre BSD De getopt (par exemple sur Mac OS X) ne supporte pas les options longues.


D'autres réponses montrent une solution pour utiliser le bâtiment bash getopts pour imiter de longues options. Cette solution fait en fait une option courte dont le caractère est" -". Donc vous obtenez " -- " comme le drapeau. Ensuite, tout ce qui suit devient OPTARG, et vous testez L'OPTARG avec un case emboîté .

c'est intelligent, mais il est livré avec des mises en garde:

  • getopts ne peut pas appliquer l'opt spec. Il ne peut pas retourner les erreurs si l'utilisateur fournit une option non valide. Vous devez faire votre propre vérification des erreurs pendant que vous parsez OPTARG.
  • OPTARG est utilisé pour le nom de l'option longue, ce qui complique l'utilisation lorsque votre option longue elle-même a un argument. Vous finissez par devoir coder cela vous-même comme un cas supplémentaire.

ainsi, Alors qu'il est possible d'écrire plus de code pour contourner la manque de support pour les options longues, c'est beaucoup plus de travail et va en partie à l'encontre de l'objectif d'utiliser un analyseur getopt pour simplifier votre code.

259
répondu Bill Karwin 2018-01-31 17:16:01

getopt et getopts sont différents des bêtes, et les gens semblent avoir un peu d'incompréhension de ce qu'ils font. getopts est une commande intégrée à bash pour traiter les options en ligne de commande dans une boucle et assigner chaque option trouvée et la valeur à son tour aux variables intégrées, de sorte que vous pouvez les traiter plus loin. getopt , cependant, est un programme d'utilité externe, et il ne traite pas réellement vos options pour vous la façon dont, par exemple bash getopts , le module Perl Getopt ou les modules Python optparse / argparse font. Tout ce que getopt fait est de canoniser les options qui sont passées - c'est à dire les convertir en une forme plus standard, de sorte qu'il est plus facile pour un script shell de les traiter. Par exemple, une application de getopt pourrait convertir ce qui suit:

myscript -ab infile.txt -ooutfile.txt

dans ceci:

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

vous devez faire le traitement vous-même. Vous n'avez pas à utiliser getopt du tout si vous faites diverses restrictions sur la façon dont vous pouvez spécifier les options:

  • seulement mis une option par l'argument;
  • toutes les options passent avant tout paramètre de position (c'est-à-dire les arguments sans option);
  • pour les options avec des valeurs (par exemple -o ci-dessus), la valeur doit aller comme un argument séparé (après un espace).

Pourquoi utiliser getopt au lieu de getopts ? La raison principale est que seul GNU getopt vous permet de prendre en charge des options en ligne de commande nommées depuis longtemps. 1 (GNU getopt est la valeur par défaut sur Linux. Mac OS X et FreeBSD sont fournis avec un getopt de base et peu utile , mais la version GNU peut être installée; voir ci-dessous.)

par exemple, voici un exemple D'utilisation de GNU getopt , d'un de mes scripts appelé 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' -- "$@"`

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 "" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY=""; shift 2 ;;
    --debugfile ) DEBUGFILE=""; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio="; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio="; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

cela vous permet de spécifier des options comme --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt" ou similaire. L'effet de l'appel à getopt est de canoniser les options à --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt" afin que vous puissiez les traiter plus facilement. Les citations autour de "" et "" est important car il assure que les arguments avec des espaces dans eux sont manipulés correctement.

si vous supprimez les 9 premières lignes (tout à travers la ligne eval set ), le code sera toujours travailler ! Cependant, votre code sera beaucoup plus précis dans les types d'options qu'il accepte: en particulier, vous devrez spécifier toutes les options dans la forme "canonique" décrite ci-dessus. Avec l'utilisation de getopt , cependant, vous pouvez grouper des options à une seule lettre, utiliser des formes non ambiguës plus courtes de long-options, utiliser soit le --file foo.txt ou --file=foo.txt style, utiliser soit le -m 4096 ou -m4096 style, des options de mélange et non-options dans n'importe quel ordre, etc. getopt aussi produit un message d'erreur si des options non reconnues ou ambiguës sont trouvées.

NOTE : il y a en fait deux totalement différentes versions de getopt , de base getopt et GNU getopt , avec des caractéristiques différentes et des conventions d'appel différentes. 2 Basic getopt est tout à fait cassé: non seulement il ne gère pas les options longues, il ne peut même pas gérer les espaces inclus à l'intérieur de arguments ou arguments vides, tandis que getopts fait ce droit. Le code ci-dessus ne fonctionnera pas dans la base getopt . GNU getopt est installé par défaut sur Linux, mais sur Mac OS X et FreeBSD il doit être installé séparément. Sur Mac OS X, installez MacPorts ( ) http://www.macports.org ), puis faites sudo port install getopt pour installer GNU getopt (habituellement dans /opt/local/bin ), et assurez-vous que /opt/local/bin est dans votre chemin de shell avant /usr/bin . Sur FreeBSD, installer misc/getopt .

un guide rapide pour modifier le code d'exemple pour votre propre programme: des quelques premières lignes, tout est" boilerplate "qui devrait rester le même, sauf la ligne qui appelle getopt . Vous devez changer le nom du programme après -n , spécifier des options courtes après -o , et des options longues après --long . Mettez deux points après les options qui prennent une valeur.

enfin, si vous voyez le code qui a juste set au lieu de eval set , il a été écrit Pour BSD getopt . Vous devriez le changer pour utiliser le style eval set , qui fonctionne très bien avec les deux versions de getopt , tandis que le simple set ne fonctionne pas correctement avec GNU getopt .

1 en fait, getopts dans ksh93 supporte les options nommées depuis longtemps, mais ce shell n'est pas utilisé aussi souvent que bash . Dans zsh , utilisez zparseopts pour obtenir ce fonctionnalité.

2 techniquement, " GNU getopt " est un terme erroné; cette version a été écrite pour Linux plutôt que pour le projet GNU. Cependant, il suit toutes les conventions GNU, et le terme "GNU getopt " est couramment utilisé (par exemple sous FreeBSD).

266
répondu Urban Vagabond 2016-09-12 05:11:52

la fonction Bash builtin getopts peut être utilisée pour analyser de longues options en mettant un caractère dash suivi d'un deux-points dans l'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: "151900920" [-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

après avoir copié sur le nom de fichier exécutable= getopts_test.sh dans le répertoire courant de travail , on peut produire la sortie comme

$ ./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'

évidemment getopts n'effectue pas de OPTERR vérification ou parsing option-argument pour les options longues. Script le fragment ci-dessus montre comment cela peut être fait manuellement. Le principe de base fonctionne aussi dans le shell Debian Almquist ("dash"). Note le cas particulier:

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

notez que, comme GreyCat de plus à http://mywiki.wooledge.org/BashFAQ souligne, ce truc exploite un comportement non standard du shell qui permet de concaténer l'option-argument (i.e. le nom du fichier dans"-f filename") à l'option (comme dans"- ffilename"). Le Le standard POSIX dit qu'il doit y avoir un espace entre les deux, ce qui dans le cas de" -- longoption " terminerait l'analyse des options et transformerait toutes les options en arguments non-optionnels.

176
répondu Arvid Requate 2014-08-10 22:58:51

la commande intégrée getopts est toujours, AFAIK, limitée aux options à un seul caractère.

Il est (ou était) un programme externe getopt qui permettrait de restructurer un ensemble d'options telles qu'il était plus facile à analyser. Vous pouvez adapter cette conception pour gérer de longues options aussi. Exemple d'utilisation:

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

# Process remaining non-option arguments
...

vous pouvez utiliser un schéma similaire avec une commande getoptlong .

noter que l'essentiel la faiblesse avec le programme externe getopt est la difficulté de gérer les arguments avec des espaces en eux, et de préserver ces espaces avec précision. C'est pourquoi le getopts intégré est supérieur, bien que limité par le fait qu'il ne traite que les options à une seule lettre.

146
répondu Jonathan Leffler 2012-05-31 14:46:59

voici un exemple qui utilise réellement getopt avec de longues options:

aflag=no
bflag=no
cargument=none

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

set -- $options

while [ $# -gt 0 ]
do
    case  in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="" ; shift;;
    (--) shift; break;;
    (-*) echo ""151900920": error - unrecognized option " 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done
71
répondu sme 2012-01-25 12:00:40

les options longues peuvent être interprétées par la norme getopts builtin comme "arguments" à la - "option

c'est portable et natif POSIX shell – aucun programme externe ou des bashismes sont nécessaires.

ce guide implémente de longues options comme arguments pour l'option - , donc --alpha est vu par getopts comme - avec l'argument alpha et --bravo=foo est vu comme - avec l'argument bravo=foo . Le vrai argument peut être récolté avec un simple remplacement: ${OPTARG#*=} .

dans cet exemple, -b (et sa forme longue, --bravo ) a une option obligatoire (noter la reconstruction manuelle de l'application que pour la forme longue). Les options non-booléennes pour les arguments longs viennent après les signes égaux, par exemple --bravo=foo (les délimiteurs d'espace pour les options longues seraient difficiles à implémenter).

parce que cela utilise getopts , cette solution soutient l'usage comme cmd -ac --bravo=foo -d FILE (qui a combiné les options -a et - c et entrelace de longues options avec les options standard) tandis que la plupart des autres réponses ici soit la lutte ou ne pas le faire.

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 $@ list

quand l'argument est un tiret ( - ), il a deux composants supplémentaires: le nom du drapeau et (optionnellement) son argument. Je les délimite de la manière habituelle n'importe quelle commande, avec le premier égal signe ( = ). $LONG_OPTARG est donc simplement le contenu de $OPTARG sans le nom du drapeau ou le signe égal.

L'intérieur case met en œuvre longue options manuellement, donc il a besoin de certains de ménage:

  • bravo=? correspond à --bravo=foo , mais pas --bravo= (note: case s'arrête après le premier match)
  • bravo* suit, notant l'argument requis manquant dans --bravo et --bravo=
  • alpha* | charlie* attrape les arguments donnés aux options qui ne les supportent pas
  • '' est présent pour supporter les non-options qui commencent par des tirets
  • * capture toutes les autres options longues et recrée l'erreur lancée par getopts pour une option invalide

Vous n'avez pas nécessairement besoin de tous ces articles d'entretien ménager. Pour exemple, peut-être voulez-vous que --bravo ait un argument optionnel (que -b ne peut pas supporter en raison d'une limitation dans getopts ). Il suffit de supprimer le =? et le cas de défaillance connexe et ensuite appeler ${ARG_B:=$DEFAULT_ARG_B} la première fois que vous utilisez $ARG_B .

34
répondu Adam Katz 2016-04-16 01:45:34

jetez un oeil à shFlags qui est une bibliothèque shell portable (ce qui signifie: sh, bash, dash, ksh, zsh sur Linux, Solaris, etc.).

il rend l'ajout de nouveaux drapeaux aussi simple que l'ajout d'une ligne à votre script, et il fournit une fonction d'utilisation générée automatiquement.

voici un simple Hello, world! utilisant 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 "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

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

pour les os qui ont amélioré getopt qui prend en charge les options longues (par exemple Linux), vous pouvez faire:

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

Pour le reste, vous devez utiliser l'option courte:

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

Ajouter un nouveau drapeau est aussi simple que d'ajouter un nouveau DEFINE_ call .

30
répondu k0pernikus 2016-03-25 23:39:29

utilisant getopts avec options et arguments courts/longs


fonctionne avec toutes les combinaisons, E. G.:

  • foobar-F -- bar
  • foobar --foo-B
  • foobar-BF -- bar -- foobar
  • foobar -fbFBAshorty --bar -FB --arguments=longhorn
  • foobar-fA "text shorty"- B -- arguments=" text longhorn "
  • bash foobar-F -- barfoo
  • sh foobar - b --foobar -...
  • bash ./ fobar-F -- bar

quelques déclarations pour ces exemples

Options=$@
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

à quoi ressemblerait la fonction D'utilisation

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 avec "long/short" drapeaux ainsi que des 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

sortie

##################################################################
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"

en Combinant le dessus dans une cohésion script

#!/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 these example ######
Options=$@
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
29
répondu RapaNui 2017-09-14 04:39:23

D'une autre façon...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done
18
répondu mtvee 2011-03-10 04:30:16

j'ai en quelque sorte résolu de cette façon:

# A string with command options
options=$@

# 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;

Je suis bête ou quoi? getopt et getopts sont tellement confus.

18
répondu Rafael 2013-09-21 10:11:22

dans le cas où vous ne voulez pas la dépendance getopt , vous pouvez faire ceci:

while test $# -gt 0
do
  case  in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option 
      ;;
    -?)
      # error unknown (short) option 
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo ""

  shift
done

bien sûr, alors vous ne pouvez pas utiliser les options de style long avec un tiret. Et si vous voulez ajouter des versions abrégées (par exemple --verbos au lieu de --verbose), alors vous devez les ajouter manuellement.

mais si vous cherchez à obtenir la fonctionnalité getopts avec de longues options, c'est une façon simple de le faire.

j'ai aussi mis cet extrait dans un gist .

13
répondu jakesandlund 2012-03-27 23:55:53

le getopts intégré ne peut pas faire cela. Il existe un programme externe getopt (1) qui peut faire cela, mais vous ne l'obtenez sur Linux à partir du paquet util-linux . Il est livré avec un exemple de script getopt l'analyser.bash .

il y a aussi une getopts_long écrite comme une fonction shell.

11
répondu Nietzche-jou 2008-12-31 06:28:55
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "" ]; do
  case  in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong "
        shift
      fi;;
    *) echo "==> other "; shift;;
  esac
done
exit
8
répondu 3ED 2010-04-09 14:54:29

Dans ksh93 , getopts prend en charge les noms longs...

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

les tutoriels que j'ai trouvés ont dit. Essayez et vous verrez.

6
répondu Richard Lynch 2012-09-12 07:46:44

Inventer encore une autre version de la roue...

cette fonction est (espérons-le) un remplacement de shell bourne simple compatible avec POSIX pour GNU getopt. Il supporte les options courtes/longues qui peuvent accepter les arguments obligatoires/optionnels/no, et la façon dont les options sont spécifiées est presque identique à GNU getopt, donc la conversion est triviale.

bien sûr, c'est encore une importante partie du code de tomber dans un script, mais c'est environ la moitié des lignes de la fonction shell bien connue de getopt_long, et pourrait être préférable dans les cas où vous voulez juste remplacer les utilisations GNU getopt existantes.

c'est un code assez nouveau, donc YMMV (et certainement s'il vous plaît faites moi savoir si ce N'est pas réellement POSIX-compatible pour n'importe quelle raison -- la portabilité était l'intention dès le début, mais je n'ai pas un environnement de test POSIX utile).

Code et exemple d'utilisation suit:

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

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # 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  after displaying error message .
exiterr () {
    printf %s\n "" >&2
    exit 
}

# 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" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts=""
    longopts=""
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                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 "" in
                                ( -* ) exiterr 1 " requires an argument";;
                                ( ?* ) arg=""; shift 2;;
                                (  * ) exiterr 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 "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option ";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "" ]; 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 "" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg=""; 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 ";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option ";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "")"; 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
}

exemple usage:

# 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 "151910920")" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=; shift 2;;
            ( -c|--client       ) useclient=1; client=; shift 2;;
            ( -s|--server       ) startserver=1; server_name=; shift 2;;
            ( -L|--load         ) load=; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi
5
répondu phils 2016-05-07 10:27:57

ici vous pouvez trouver quelques approches différentes pour l'analyse des options complexes en bash: http://mywiki.wooledge.org/ComplexOptionParsing

j'ai créé le suivant, et je pense que c'est un bon, parce que c'est un code minimal et les options longues et courtes fonctionnent. Une option longue peut aussi avoir plusieurs arguments avec cette approche.

#!/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: "151900920" [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file
3
répondu user3573558 2014-05-01 19:25:55

Je n'écris que des scripts shell de temps en temps et je ne m'entraîne plus, donc tout feedback est apprécié.

en utilisant la stratégie proposée par @Arvid Requate, nous avons remarqué quelques erreurs de l'utilisateur. Un utilisateur qui oublie d'inclure une valeur aura accidentellement le nom de l'option suivante traité comme une valeur:

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

fera apparaître la valeur de" loglevel "comme"--toc=TRUE". Cela peut être évitée.

j'ai adapté quelques idées sur vérifier l'erreur de L'utilisateur pour CLI de http://mwiki.wooledge.org/BashFAQ/035 discussion of manual parsing. J'ai inséré la vérification des erreurs dans la gestion des arguments "-" et"--".

puis j'ai commencé à tripoter la syntaxe, donc toutes les erreurs ici sont strictement de ma faute, pas les auteurs originaux.

Mon approche aide les utilisateurs qui préfèrent entrer dans de longs avec ou sans le signe égal. C'est, il doit avoir la même réponse à "--loglevel 9" "--loglevel=9". Dans la méthode --/space, il n'est pas possible de savoir avec certitude si l'utilisateur oublie un argument, il faut donc tenter de deviner.

  1. si l'Utilisateur a le format de signe long/égal (--opt=), alors un espace après = déclenche une erreur parce qu'un argument n'a pas été fourni.
  2. si l'Utilisateur a des arguments long/space (--opt ), ce script provoque un échec si aucun argument ne suit (fin de commande) ou si l'argument commence par dash)

dans le cas où vous commencez sur ce, il y a une différence intéressante entre les formats "--opt=value" et "--opt value". Avec le signe égal, l'argument de ligne de commande est vu comme "opt=value" et le travail à traiter qui est l'analyse de chaîne, pour séparer au "=". En revanche, avec "-- opt value", le nom de l'argument est" opt " et nous avons le défi d'obtenir la valeur suivante fournie dans la ligne de commande. C'est là que @Arvid Requate a utilisé $ {!OPTIND}, la référence indirecte. Je ne comprends toujours pas que, bien, du tout, et les commentaires dans BashFAQ semblent mettre en garde contre ce style ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, Je ne pense pas que les commentaires de l'affiche précédente sur L'importance de OPTIND=$(( $OPTIND + 1 )) sont corrects. Je veux dire, je ne vois pas de mal de l'omission.

dans la version la plus récente de ce script, flag-v signifie impression verbeuse.

enregistrez-le dans un fichier appelé "cli-5.sh", le rendre exécutable, et un de ces fonctionnera ou pas de la manière désirée

./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

voici un exemple de sortie du contrôle d'erreur sur l'utilisateur 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

vous devriez envisager de tourner sur-v, parce qu'il imprime les internes de OPTIND et OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /q/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options-25616/"${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


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

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

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "" >&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: "151930920" [-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: "151930920" [-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"
3
répondu pauljohn32 2017-10-17 15:00:43

je travaille sur ce sujet depuis assez longtemps... et fait ma propre bibliothèque que vous aurez besoin de source dans votre script principal. Voir libopt4shell et cd2mpc pour un exemple. Espérons que cela aide !

2
répondu liealgebra 2011-03-11 10:59:42

une solution améliorée:

# 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 "" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]=""  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]=""  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

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

# 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))
[[ "" == "--" ]] && shift
2
répondu jbar 2011-07-16 10:37:36

peut-être qu'il est plus simple d'utiliser ksh, juste pour la partie getopts, si besoin des options de longue ligne de commande, car il peut être plus facile à faire là.

# 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
2
répondu efstathiou_e 2012-06-26 14:49:28

je voulais quelque chose sans dépendances externes, avec un support bash strict (-u), et j'en avais besoin pour travailler même sur les versions plus anciennes de bash. Il s'occupe de divers types de params:

  • court bool (-h)
  • options courtes (-i "de l'image.jpg")
  • longs bols (--aide)
  • égale options (--file="nom du fichier.ext")
  • l'espace des options (--fichier "nom de fichier.ext")
  • bools concatinés (- hvm)

insérez simplement ce qui suit en haut de votre 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  ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "" ]] ; 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  ]] ; 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 "" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "" ]; then
          # The next argument has NO preceding dash so it is a value
          value=""
          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' "" >&2
    fi
    shift $shift_count
  done
}

et l'utiliser comme suit:

# 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 $*
2
répondu Heath Dutton 2015-07-02 18:57:31

Je n'ai pas encore assez de représentants pour commenter ou voter sa solution, mais réponse de PME a très bien fonctionné pour moi. Le seul problème que j'ai rencontré était que les arguments finissent enveloppé dans des guillemets simples (j'ai donc une bande).

j'ai aussi ajouté des exemples d'utilisation et du texte D'aide. Je vais inclure ma version légèrement prolongée ici:

#!/bin/bash

# getopt example
# from: /q/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options-25616/"   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: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

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

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

done
1
répondu kghastie 2017-05-23 12:34:45

afin de rester compatible avec toutes les plates-formes et d'éviter de dépendre d'exécutables externes, j'ai porté du code d'une autre langue.

je le trouve très facile à utiliser, voici un exemple:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

la BASH requise est un peu plus longue qu'elle ne pourrait l'être, mais je voulais éviter de m'appuyer sur les tableaux associatifs de BASH 4. Vous pouvez aussi le télécharger directement à partir de http://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function ""151910920"" is missing parameters 
        return 1
    }

    local delimiter=""
    local string=""
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "" && upvar  "value(s)"
upvar() {
    if unset -v ""; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval =\"$2\"          # Return single value
        else
            eval =\(\"${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "" == "${EXPLODED[0]}" ]
            then
                decho "Matched  with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "" == "${EXPLODED[1]}" ]
            then
                decho "Matched  with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "" ]
    do
        # echo "Processing "
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=
        else
            decho "Not argument or option: ''" >& 2
            __argparser__argv+=( "" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option ''" >&2 # && __argparser__argv+=( "" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of ''"   >&2 && ArgParser::set "" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "" && upvar  "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|||")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename ""151910920"" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi
0
répondu Orwellophile 2013-03-16 10:02:31

si toutes vos options longues ont unique, et correspondant, premiers caractères comme les options courtes, donc par exemple

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

est le même que

./slamm -c 23 -p test -q

vous pouvez utiliser ce avant getopts pour réécrire $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
    .....

Merci pour mtvee pour l'inspiration ;-)

0
répondu commonpike 2013-09-21 10:11:58

Builtin getopts analyser uniquement les fichiers à court d'options (sauf dans ksh93), mais vous pouvez encore ajouter quelques lignes de script pour faire getopts gère de longues options.

Voici une partie du code trouvé dans http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

voici un test:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

autrement dans la récente coquille de Korn ksh93, getopts peut naturellement analyser de longues options et même afficher une page de manuel de la même façon. (Voir http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )

0
répondu Michel VONGVILAY uxora.com 2015-05-19 13:33:33

Th OS X intégré (BSD) getopt ne supporte pas les options longues, mais la version GNU le fait: brew install gnu-getopt . Puis, quelque chose de similaire à: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt .

0
répondu wprl 2016-05-18 15:15:24

EasyOptions poignées de courte et de longue options:

## 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
0
répondu Renato Silva 2016-07-04 15:24:18

getopts "pourrait être utilisé" pour l'analyse de longs tant que vous ne vous attendez pas à avoir des arguments...

Voici comment faire:

$ 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

si vous essayez D'utiliser OPTIND pour obtenir un paramètre pour l'option long, getopts le traitera comme le premier paramètre de position non optionnel et arrêtera d'analyser tout autre paramètre. Dans un tel cas, il est préférable de le traiter manuellement avec une simple instruction de cas.

Cela fonctionnera "toujours":

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

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter ; 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

bien que n'est pas aussi flexible que getopts et vous devez faire la plupart du code de vérification d'erreur vous-même dans les instances de cas...

Mais c'est une option.

-1
répondu estani 2012-02-01 14:13:09

hm.

pas vraiment satisfait des options pures bash. pourquoi ne pas utiliser perl pour obtenir ce que vous voulez. Analysez directement le tableau$*, et auto-nommez vos options.

simple script d'aide:

#!/usr/bin/perl
use Getopt::Long;

my $optstring = shift;

my @opts = split(m#,#, $optstring);

my %opt;
GetOptions(\%opt, @opts);

print "set -- " . join(' ', map("'$_'", @ARGV)) . ";";
my $xx;

my $key;
foreach $key (keys(%opt))
{
    print "export $key='$opt{$key}'; ";
}

alors vous pouvez utiliser dans votre script comme un liner, par exemple:

#!/bin/bash

eval `getopts.pl reuse:s,long_opt:s,hello $*`;

echo "HELLO: $hello"
echo "LONG_OPT: $long_opt"
echo "REUSE: $reuse"

echo $*

/tmp/script.sh bonjour --réutilisation moi --long_opt whatever_you_want_except_spaces --bonjour 1 2 3

HELLO: 1 LONG_OPT: whatever_you_want_except spaces Réutilisation: me

1 2 3

la seule mise en garde est que les espaces ne fonctionnent pas. Mais il évite la syntaxe de boucle assez compliquée de bash, fonctionne avec de longues args, les nomme automatiquement comme variables et redimensionne automatiquement $*, donc fonctionnera 99% du temps.

-1
répondu Edward Peschko 2015-05-19 17:22:26