513 votes

Utilisation de getopts pour traiter les options de ligne de commande longues et courtes

Je souhaite avoir des formes longues et courtes d'options de ligne de commande invoquées à l'aide de mon shell script.

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

Avez-vous une idée de la façon dont cela peut être fait, afin que je puisse utiliser des options telles que :

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

Dans l'exemple ci-dessus, les deux commandes signifient la même chose pour mon shell, mais en utilisant getopts Je n'ai pas été en mesure de les mettre en œuvre ?

5 votes

À mon avis, la réponse acceptée n'est pas la meilleure. Elle ne montre pas comment utiliser getopts pour gérer les arguments "-" et "--", ce qui peut être fait, comme l'a démontré @Arvid Requate. J'insère une autre réponse qui utilise un concept similaire, mais qui traite également de l'erreur de l'utilisateur qui "oublie" d'insérer des valeurs pour les arguments qui sont nécessaires. Point clé : les getopts peuvent être utilisés. L'utilisateur doit éviter d'utiliser "getopt" à la place si la portabilité multiplateforme est nécessaire. De plus, getopts fait partie du standard POSIX pour les shells, il est donc probable qu'il soit portable.

0 votes

400voto

Urban Vagabond Points 1936

getopt y getopts sont des bêtes différentes, et les gens semblent avoir un peu de mal à comprendre ce qu'ils font. getopts est une commande intégrée à bash pour traiter les options de la ligne de commande dans une boucle et affecter chaque option et valeur trouvée à des variables intégrées, afin de pouvoir les traiter ultérieurement. getopt est cependant un programme utilitaire externe, et il est le seul à pouvoir être utilisé. ne traite pas réellement vos options pour vous de la manière dont, par exemple, bash getopts le langage Perl Getopt ou le module Python optparse / argparse les modules le font. Tout cela getopt fait est de canoniser les options qui sont passées - c'est-à-dire de les convertir en une forme plus standard, afin qu'il soit 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 imposez diverses restrictions sur la façon dont vous pouvez spécifier les options :

  • ne mettez qu'une seule option par argument ;
  • toutes les options passent avant tous les paramètres positionnels (c'est-à-dire les arguments sans option) ;
  • pour les options avec des valeurs (par exemple -o ci-dessus), la valeur doit être présentée 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 les options de ligne de commande aux noms longs. 1 (GNU getopt est la valeur par défaut sous Linux. Mac OS X et FreeBSD sont livrés avec une version basique et peu utile de l'outil de gestion de l'information. getopt mais la version GNU peut être installée ; voir ci-dessous).

Par exemple, voici un exemple d'utilisation de GNU getopt à partir d'un script de moi 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 "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

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 en --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 "$1" y "$2" est important car il garantit que les arguments contenant des espaces sont traités correctement.

Si vous supprimez les 9 premières lignes (tout ce qui va jusqu'au eval set ), le code sera toujours fonctionner ! Cependant, votre code sera beaucoup plus pointilleux quant aux types d'options qu'il accepte : En particulier, vous devrez spécifier toutes les options sous la forme "canonique" décrite ci-dessus. Avec l'utilisation de getopt Cependant, vous pouvez regrouper des options à une seule lettre, utiliser des formes plus courtes et non ambiguës de longues options, utiliser l'option --file foo.txt o --file=foo.txt utilisez soit le -m 4096 o -m4096 style, mélanger des options et des non-options dans n'importe quel ordre, etc. getopt produit également un message d'erreur si des options non reconnues ou ambiguës sont trouvées.

NOTE : Il y a en fait deux totalement différent des versions de getopt , de base getopt et GNU getopt avec différentes caractéristiques et différentes conventions d'appel. 2 Base getopt est bien cassé : Non seulement il ne gère pas les options longues, mais il ne peut même pas gérer les espaces incorporés à l'intérieur des arguments ou les arguments vides, alors que getopts fait bien les choses. Le code ci-dessus ne fonctionnera pas dans Basic getopt . GNU getopt est installé par défaut sous Linux, mais sous Mac OS X et FreeBSD, il doit être installé séparément. Sous Mac OS X, installez MacPorts ( http://www.macports.org ) et ensuite faire sudo port install getopt pour installer GNU getopt (généralement en /opt/local/bin ), et assurez-vous que /opt/local/bin se trouve dans le chemin du shell avant /usr/bin . Sous FreeBSD, installez misc/getopt .

Un guide rapide pour modifier le code d'exemple pour votre propre programme : Dans les premières lignes, tout est du "boilerplate" qui doit rester le même, sauf la ligne qui appelle getopt . Vous devez changer le nom du programme après -n spécifier les 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 du code qui vient set au lieu de eval set il a été écrit pour BSD getopt . Vous devez le modifier pour utiliser le eval set qui fonctionne parfaitement avec les deux versions de getopt tandis que la plaine set ne fonctionne pas correctement avec GNU getopt .

1 En fait, getopts en ksh93 prend en charge les options aux noms longs, mais cet interpréteur de commandes n'est pas utilisé aussi souvent que le shell bash . Sur zsh utiliser zparseopts pour obtenir cette fonctionnalité.

2 Techniquement, "GNU getopt "Cette version a en fait été écrite pour Linux plutôt que pour le projet GNU. Cependant, elle suit toutes les conventions GNU, et le terme " GNU getopt " est couramment utilisé (par exemple sous FreeBSD).

4 votes

Cela a été très utile, l'idée d'utiliser getopt pour vérifier les options et ensuite traiter ces options dans une boucle très simple a vraiment bien fonctionné lorsque j'ai voulu ajouter des options de style long à un bash script. Merci.

6 votes

getopt sous Linux est no un utilitaire GNU et le traditionnel getopt ne provient pas initialement de BSD mais d'AT&T Unix. L'article de ksh93 getopts (également de AT&T) supporte les options longues de style GNU.

0 votes

@StephaneChazelas -- édité pour refléter vos commentaires. Je préfère toujours le terme "GNU getopt" même si c'est un terme inapproprié, parce que cette version suit les conventions GNU et se comporte généralement comme un programme GNU (par exemple, en faisant usage de POSIXLY_CORRECT ), tandis que "Linux-enhanced getopt" suggère à tort que cette version n'existe que sous Linux.

343voto

Bill Karwin Points 204877

Trois mises en œuvre peuvent être envisagées :

  • Bash builtin getopts . Il ne prend pas en charge les noms d'options longs avec le préfixe double tiret. Elle ne prend en charge que les options à un seul caractère.

  • Mise en œuvre BSD UNIX de l'autonomie getopt (c'est ce que MacOS utilise). Cette commande ne prend pas non plus en charge les options longues.

  • Implémentation GNU d'un système autonome getopt . GNU getopt(3) (utilisé par la ligne de commande getopt(1) sur Linux) prend en charge l'analyse des options longues.


D'autres réponses montrent une solution pour utiliser le buildin bash. getopts pour imiter les options longues. Cette solution crée en fait une option courte dont le caractère est "-". Vous obtenez donc "--" comme drapeau. Ensuite, tout ce qui suit devient OPTARG, et vous testez l'OPTARG avec une méthode imbriquée case .

C'est intelligent, mais il y a des réserves :

  • getopts ne peut pas appliquer la spécification d'option. Il ne peut pas renvoyer d'erreurs si l'utilisateur fournit une option invalide. Vous devez faire votre propre contrôle d'erreur lorsque vous analysez OPTARG.
  • OPTARG est utilisé pour le nom de l'option longue, ce qui complique l'utilisation lorsque votre option longue a elle-même un argument. Vous finissez par devoir coder cela vous-même comme un cas supplémentaire.

Ainsi, bien qu'il soit possible d'écrire plus de code pour contourner le manque de support pour les options longues, cela représente beaucoup plus de travail et va partiellement à l'encontre de l'objectif d'utiliser un analyseur getopt pour simplifier votre code.

40 votes

Alors. Quelle est la solution portable et multiplateforme ?

8 votes

GNU Getopt semble être le seul choix possible. Sur Mac, installez GNU getopt à partir de macports. Sous Windows, j'installerais GNU getopt avec Cygwin.

2 votes

Apparemment , ksh getopts peut gérer les options longues.

237voto

Arvid Requate Points 441

La fonction intégrée getopts de Bash peut être utilisée pour analyser les options longues en plaçant un tiret suivi de 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: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

Après avoir copié dans le fichier exécutable nom= getopts_test.sh en el le répertoire de travail actuel on peut produire des résultats 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'

De toute évidence, getopts n'exécute ni l'un ni l'autre OPTERR ni la vérification ni l'analyse des arguments d'option pour les options longues. Le fragment script ci-dessus montre comment cela peut être fait manuellement. Le principe de base fonctionne également dans le shell Debian Almquist ("dash"). Notez 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 de à http://mywiki.wooledge.org/BashFAQ souligne que cette astuce exploite un comportement non standard de l'interpréteur de commandes qui permet à l'argument d'option (c'est-à-dire le nom de fichier dans "-f nomdefichier") d'être concaténé à l'option (comme dans "-ffilename"). Le site POSIX Le standard dit qu'il doit y avoir un espace entre eux, ce qui dans le cas de "-- longoption" mettrait fin à l'analyse des options et transformerait toutes les longoptions en arguments non-option.

2 votes

Une question : quelle est la sémantique de ! en val="${!OPTIND} ?

2 votes

@TomRoche c'est la substitution indirecte : unix.stackexchange.com/a/41293/84316

0 votes

J'ai cependant ma propre question. Lorsque l'option --loglevel long est analysée, pourquoi dois-je incrémenter la valeur de OPTIND ? Ne puis-je pas simplement laisser OPTIND tel quel ?

147voto

Jonathan Leffler Points 299946

Le système intégré getopts est toujours, AFAIK, limitée à des options à un seul caractère.

Il existe (ou existait) un programme externe. getopt qui réorganiserait un ensemble d'options de façon à ce qu'il soit plus facile à analyser. Vous pourriez adapter cette conception pour gérer les options longues également. Exemple d'utilisation :

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

# Process remaining non-option arguments
...

Vous pourriez utiliser un schéma similaire avec un getoptlong commandement.

Notez que la faiblesse fondamentale avec l'extérieur getopt est la difficulté de traiter les arguments contenant des espaces, et de préserver ces espaces avec précision. C'est pourquoi la fonction intégrée getopts est supérieure, bien que limitée par le fait qu'elle ne traite que les options à une seule lettre.

11 votes

Getopt, sauf pour la version GNU (qui a une convention d'appel différente), est fondamentalement cassé. Ne l'utilisez pas. Veuillez utiliser **getopts à la place bash-hackers.org/wiki/doku.php/howto/getopts_tutorial

9 votes

@hendry - à partir de votre propre lien : "Notez que getopts n'est pas capable d'analyser les options longues de style GNU (--myoption) ou les options longues de style XF86 (-myoption) !"

1 votes

Jonathan -- tu devrais réécrire l'exemple pour utiliser eval set avec des guillemets (voir ma réponse ci-dessous) pour qu'il fonctionne aussi correctement avec GNU getopt (la valeur par défaut sous Linux) et gère correctement les espaces.

80voto

sme Points 1992

Voici un exemple qui utilise réellement getopt avec des options longues :

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 $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1 votes

Vous devriez réécrire l'exemple pour utiliser eval set avec des guillemets (voir ma réponse ci-dessous) pour qu'il fonctionne aussi correctement avec GNU getopt (la valeur par défaut sous Linux) et gère correctement les espaces.

3 votes

Il s'agit d'utiliser getopt alors que la question porte sur getopts cependant.

1 votes

Sont (-- , (-* y (* des modèles valables ? En quoi sont-ils différents de -- , -* y * ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X