Je souhaite que des formes longues et courtes d'options de ligne de commande soient appelées à l'aide de mon script Shell.
Je sais que getopts
peut être utilisé, mais comme dans Perl, je n’ai pas pu faire de même avec Shell.
Toutes les idées sur 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 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é en mesure de les implémenter?
Trois mises en œuvre peuvent être envisagées:
Bash intégré dans getopts
. Cela ne prend pas en charge les noms d'option longs avec le préfixe de double tiret. Il ne supporte que les options à un seul caractère.
Implémentation BSD UNIX de la commande autonome getopt
(utilisée par MacOS). Cela ne supporte pas les options longues non plus.
Implémentation GNU de la version autonome getopt
. GNU getopt(3)
(utilisé par la ligne de commande getopt(1)
sur Linux) prend en charge l'analyse d'options longues.
Certaines autres réponses montrent une solution pour utiliser la variable __ _getopts
intégrée dans bash pour imiter les options longues. Cette solution crée en fait une option courte dont le caractère est "-". Donc, vous obtenez "-" comme drapeau. Ensuite, tout ce qui suit devient OPTARG et vous testez OPTARG avec une case
imbriquée.
Ceci est intelligent, mais il vient avec des mises en garde:
getopts
ne peut pas appliquer la spécification opt. Il ne peut pas renvoyer d'erreurs si l'utilisateur fournit une option non valide. Vous devez vérifier vous-même les erreurs lorsque vous analysez OPTARG.Ainsi, s’il est possible d’écrire plus de code pour pallier le manque de prise en charge des options longues, c’est beaucoup plus de travail et rend partiellement caduque l’utilisation d’un analyseur getopt pour simplifier votre code.
getopt
et getopts
sont des animaux différents, et les gens semblent avoir un peu de mal à comprendre ce qu'ils font. getopts
est une commande intégrée permettant à bash
de traiter les options de ligne de commande dans une boucle et d'assigner chaque option trouvée et chaque valeur trouvée à des variables intégrées afin que vous puissiez les traiter ultérieurement. getopt
, cependant, est un programme utilitaire externe, et il {ne traite pas réellement vos options pour vous} _ la manière dont par exemple. bash getopts
, le module Perl Getopt
ou les modules Python optparse
/argparse
Tout ce que getopt
fait, c'est de canoniser les options qui sont passées - c'est-à-dire de les convertir en un formulaire plus standard, de sorte qu'il est plus facile pour un script Shell de les traiter. Par exemple, une application de getopt
peut 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 du tout à utiliser getopt
si vous définissez diverses restrictions sur la manière de spécifier des options:
-o
ci-dessus), la valeur doit être 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 portant un nom long.1 (GNU getopt
est la valeur par défaut sous Linux. Mac OS X et FreeBSD sont livrés avec une getopt
de base peu utile, mais la version GNU peut être installée; voir ci-dessous.)
Par exemple, voici un exemple d'utilisation de GNU getopt
, à partir 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 "$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 telles que --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"
ou similaire. L’appel de getopt
a pour effet de canoniser les options de --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"
afin que vous puissiez les traiter plus facilement. Les guillemets autour de "$1"
et "$2"
sont importants car ils permettent de gérer correctement les arguments contenant des espaces.
Si vous supprimez les 9 premières lignes (tout le long de la ligne eval set
), le code sera fonctionne toujours! Cependant, votre code sera beaucoup plus sélectif quant aux types d’options qu’il accepte: En particulier, vous devrez spécifier toutes les options dans la forme "canonique" décrite ci-dessus. Cependant, avec getopt
, vous pouvez grouper des options à lettre unique, utiliser des formes courtes non ambiguës d’options longues, utiliser le style --file foo.txt
ou --file=foo.txt
, utiliser le style -m 4096
ou -m4096
, N'importe quel ordre, etc. getopt
génère également un message d'erreur si des options non reconnues ou ambiguës sont trouvées.
NOTE: Il existe actuellement deux totalement différentes versions de getopt
, de base getopt
et de GNU getopt
, avec des fonctionnalités et des conventions d'appel différentes.2 getopt
de base est tout à fait 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 d'arguments ou d'arguments vides, alors que getopts
le fait correctement. Le code ci-dessus ne fonctionnera pas dans getopt
de base. GNU getopt
est installé par défaut sous Linux, mais sous Mac OS X et FreeBSD, il doit être installé séparément. Sur Mac OS X, installez MacPorts ( http://www.macports.org ) puis exécutez Sudo port install getopt
pour installer GNU getopt
(généralement dans /opt/local/bin
) et assurez-vous que /opt/local/bin
est dans le chemin de votre shell. devant /usr/bin
. Sur FreeBSD, installez misc/getopt
.
Un guide rapide pour modifier l’exemple de code de votre propre programme: Sur les quelques premières lignes, tout est "passe-partout" et devrait rester le même, à l’exception de la ligne qui appelle getopt
. Vous devez changer le nom du programme après -n
, spécifier les options courtes après -o
et les options longues après --long
. Placez deux points après les options qui prennent une valeur.
Enfin, si vous voyez du 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 bien avec les deux versions de getopt
, alors que le plain set
ne fonctionne pas correctement avec GNU getopt
.
1En fait, getopts
dans ksh93
prend en charge les options portant un nom long, mais ce shell n'est pas utilisé aussi souvent que bash
. Dans zsh
, utilisez zparseopts
pour obtenir cette fonctionnalité.
2Techniquement, "GNU getopt
" est un terme impropre; 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).
La fonction getopts intégrée à Bash peut être utilisée pour analyser de longues options en insérant un tiret suivi de deux points dans 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é le fichier exécutable nom = getopts_test.sh
dans le répertoire de travail courant , vous pouvez générer une sortie telle que
$ ./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’effectue ni la vérification OPTERR
, ni l’analyse des arguments-options pour les options longues. Le fragment de 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 le souligne GreyCat à l'adresse http://mywiki.wooledge.org/BashFAQ , cette astuce exploite un comportement non standard du shell qui autorise l'option-argument (le nom de fichier dans "- f nomfichier ") à concaténer avec l'option (comme dans" -ffilename "). La norme POSIX indique 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 options longues en arguments non-option.
La commande getopts
intégrée est toujours, autant que je sache, limitée aux options à un seul caractère.
Il existe (ou existait auparavant) un programme externe getopt
qui réorganiserait un ensemble d'options de manière à ce qu'il soit plus facile à analyser. Vous pouvez également adapter cette conception aux options longues. 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 pouvez utiliser un schéma similaire avec une commande getoptlong
.
Notez que la faiblesse fondamentale du programme getopt
externe est la difficulté de gérer les arguments contenant des espaces et de les conserver avec précision. C'est pourquoi la variable getopts
intégrée est supérieure, bien que limitée par le fait qu'elle ne gère que les options à lettre unique.
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 $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
Les options longues peuvent être analysées par la variable standard getopts
intégrée en tant qu '«arguments» à l'option -
«option».
Ceci est POSIX Shell portable et natif - aucun programme externe ni bashisme n’est nécessaire.
Ce guide implémente les options longues en tant qu'arguments de l'option -
. De ce fait, --alpha
est vu par getopts
sous la forme -
avec l'argument alpha
et --bravo=foo
est considéré 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 (notez la reconstruction manuelle de son application pour la forme longue). Les options non booléennes aux arguments longs viennent après les signes d'égalité, par exemple. --bravo=foo
(les délimiteurs d'espace pour les options longues seraient difficiles à implémenter).
Comme cela utilise getopts
, cette solution prend en charge une utilisation telle que cmd -ac --bravo=foo -d FILE
(qui combine les options -a
et -c
et entrelace les options longues avec les options standard), alors que la plupart des autres réponses ne répondent pas ou ne parviennent 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
Lorsque l'argument est un tiret (-
), il comporte deux composants supplémentaires: le nom de l'indicateur et (éventuellement) son argument. Je les délimite comme n'importe quelle commande avec le premier signe égal (=
). $LONG_OPTARG
est donc simplement le contenu de $OPTARG
sans le nom du drapeau ou le signe égal.
case
inner implémente manuellement les options longues, de sorte qu'il a besoin de quelques tâches de maintenance:
bravo=?
correspond à --bravo=foo
mais pas --bravo=
(remarque: case
s'arrête après le premier match)bravo*
suit, en notant l’argument requis manquant dans --bravo
et --bravo=
alpha* | charlie*
intercepte les arguments donnés aux options qui ne les supportent pas''
est présent pour supporter les non-options qui commencent par des tirets*
intercepte toutes les autres options longues et recrée l'erreur générée par getopts pour une option non valideVous n'avez pas nécessairement besoin de tous ces articles d'entretien ménager. Par exemple, vous souhaitez peut-être que --bravo
ait un argument optional (que -b
ne peut pas prendre en charge en raison d'une limitation dans getopts
). Supprimez simplement le =?
et le cas d'échec associé, puis appelez ${ARG_B:=$DEFAULT_ARG_B}
la première fois que vous utilisez $ARG_B
.
Jetez un coup d'œil à shFlags, qui est une bibliothèque Shell portable (ce qui signifie: sh, bash, dash, ksh, zsh sous Linux, Solaris, etc.).
L'ajout de nouveaux indicateurs est aussi simple que l'ajout d'une ligne à votre script et 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 systèmes d’exploitation ayant le getopt amélioré qui prend en charge les options longues (par exemple, Linux), vous pouvez effectuer les tâches suivantes:
$ ./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 nouvel indicateur est aussi simple que d’ajouter un nouveau DEFINE_ call
.
getopts
avec des options et des arguments courts/longsOptions=$@
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 "
getops
avec des drapeaux long/short ainsi que des arguments longswhile 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
##################################################################
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"
#!/bin/bash
# foobar: getopts with short and long options AND arguments
function _cleanup ()
{
unset -f _usage _cleanup ; return 0
}
## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN
###### some declarations for this example ######
Options=$@
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
Autrement...
# 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
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;
Suis-je bête ou quelque chose? getopt
et getopts
sont tellement déroutants.
Si vous ne voulez pas la dépendance getopt
, vous pouvez faire ceci:
while test $# -gt 0
do
case $1 in
# Normal option processing
-h | --help)
# usage and help
;;
-v | --version)
# version info
;;
# ...
# Special cases
--)
break
;;
--*)
# error unknown (long) option $1
;;
-?)
# error unknown (short) option $1
;;
# FUN STUFF HERE:
# Split apart combined short options
-*)
split=$1
shift
set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
continue
;;
# Done with options
*)
break
;;
esac
# for testing purposes:
echo "$1"
shift
done
Bien sûr, vous ne pouvez pas utiliser les options de style long avec un tiret. Et si vous souhaitez ajouter des versions abrégées (par exemple --verbos au lieu de --verbose), vous devez les ajouter manuellement.
Mais si vous souhaitez obtenir la fonctionnalité getopts
avec des options longues, il s'agit d'un moyen simple de le faire.
J'ai également mis cet extrait dans un Gist .
La variable getopts
intégrée ne peut pas le faire. Il y a un externe getopt(1) programme capable de le faire, mais vous ne l’obtenez que sous Linux util-linux paquet. Il vient avec un exemple de script getopt-parse.bash.
Il existe également un getopts_long
écrit en tant que fonction Shell.
#!/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 "$1" ]; do
case $1 in
"--dlong")
shift
if [ "${1:1:0}" != "-" ]
then
echo "==> dlong $1"
shift
fi;;
*) echo "==> other $1"; shift;;
esac
done
exit
Dans ksh93
, getopts
prend en charge les noms longs ...
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
Ou alors les tutoriels que j'ai trouvés ont dit. Essayez et voyez.
Inventer encore une autre version de la roue ...
Cette fonction est un (dans l’espoir) un remplacement simple de shell compatible avec POSIX pour GNU getopt. Il supporte les options courtes/longues pouvant accepter des arguments obligatoires/optionnels/non, et la façon dont les options sont spécifiées est presque identique à GNU getopt, ce qui rend la conversion triviale.
Bien sûr, cela reste un gros morceau de code à insérer dans un script, mais il représente environ la moitié des lignes de la fonction bien connue getopt_long Shell, et peut être préférable dans les cas où vous souhaitez simplement remplacer le GNU existant. les usages.
C'est un nouveau code, donc YMMV (et indiquez-moi s'il ne s'agit pas d'une compatibilité POSIX pour quelque raison que ce soit - la portabilité était l'intention initiale, mais je n'ai pas d'environnement de test POSIX utile).
Le code et les exemples d'utilisation suivent:
#!/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 $1 after displaying error message $2.
exiterr () {
printf %s\\n "$2" >&2
exit $1
}
# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# 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="$1"
longopts="$2"
shift 2
getopt=
nonopt=
while [ $# -gt 0 ]; do
opt=
arg=
argtype=
case "$1" in
# '--' means don't parse the remaining options
( -- ) {
getopt="${getopt}$(save "$@")"
shift $#
break
};;
# process short option
( -[!-]* ) { # -x[foo]
suffix=${1#-?} # foo
opt=${1%$suffix} # -x
optchar=${opt#-} # x
case "${shortopts}" in
( *${optchar}::* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *${optchar}:* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 "$1 requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 "$1 requires an argument";;
esac
fi
};;
( *${optchar}* ) { # no argument
argtype=none
arg=
shift
# Handle multiple no-argument parameters combined as
# -xyz instead of -x -y -z. If we have just shifted
# parameter -xyz, we now replace it with -yz (which
# will be processed in the next iteration).
if [ -n "${suffix}" ]; then
eval "set -- $(save "-${suffix}")$(save "$@")"
fi
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# process long option
( --?* ) { # --xarg[=foo]
suffix=${1#*=} # foo (unless there was no =)
if [ "${suffix}" = "$1" ]; then
suffix=
fi
opt=${1%=$suffix} # --xarg
optword=${opt#--} # xarg
case ",${longopts}," in
( *,${optword}::,* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *,${optword}:,* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 \
"--${optword} requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 \
"--${optword} requires an argument";;
esac
fi
};;
( *,${optword},* ) { # no argument
if [ -n "${suffix}" ]; then
exiterr 1 "--${optword} does not take an argument"
fi
argtype=none
arg=
shift
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# any other parameters starting with -
( -* ) exiterr 1 "Unknown option $1";;
# remember non-option parameters
( * ) nonopt="${nonopt}$(save "$1")"; shift;;
esac
if [ -n "${opt}" ]; then
getopt="${getopt}$(save "$opt")"
case "${argtype}" in
( optional|required ) {
getopt="${getopt}$(save "$arg")"
};;
esac
fi
done
# Generate function output, suitable for:
# eval "set -- $(posix_getopt ...)"
printf %s "${getopt}"
if [ -n "${nonopt}" ]; then
printf %s "$(save "--")${nonopt}"
fi
}
Exemple d'utilisation:
# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
#eval set -- ${opts}
eval "set -- ${opts}"
while [ $# -gt 0 ]; do
case "$1" in
( -- ) shift; break;;
( -h|--help ) help=1; shift; break;;
( -v|--version ) version_help=1; shift; break;;
( -d|--directory ) dir=$2; shift 2;;
( -c|--client ) useclient=1; client=$2; shift 2;;
( -s|--server ) startserver=1; server_name=$2; shift 2;;
( -L|--load ) load=$2; shift 2;;
( -D|--delete ) delete=1; shift;;
esac
done
else
shorthelp=1 # getopt returned (and reported) an error.
fi
Je n’écris que de temps en temps des scripts Shell et j’échappe à la pratique, tout retour d’information est apprécié.
En utilisant la stratégie proposée par @Arvid Requate, nous avons remarqué quelques erreurs d'utilisateurs. 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
la valeur de "loglevel" sera considérée comme "--toc = TRUE". Cela peut être évité.
J'ai adapté quelques idées sur la vérification des erreurs utilisateur pour CLI à partir de la discussion http://mwiki.wooledge.org/BashFAQ/035 sur l'analyse manuelle. J'ai inséré une erreur de vérification dans la gestion des deux "-" et "-" arguments.
Puis j'ai commencé à jouer avec 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 longtemps avec ou sans le signe égal. C'est-à-dire qu'il devrait avoir la même réponse à "--loglevel 9" que "--loglevel = 9". Dans la méthode -/space, il n'est pas possible de savoir avec certitude si l'utilisateur oublie un argument, il est donc nécessaire de deviner.
Dans le cas où vous débutez sur ce sujet, il existe une différence intéressante entre les formats "--opt = value" et "--opt value". Avec le signe égal, l'argument de ligne de commande est considéré comme "opt = valeur" et le travail à gérer qui consiste à analyser la chaîne, à séparer au "=". En revanche, avec "--opt value", le nom de l'argument est "opt" et le défi consiste à 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 cela. 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 des précédentes affiches sur l'importance d'OPTIND = $ (($ OPTIND + 1)) soient corrects. Je veux dire, je ne vois pas de mal à l'omettre.
Dans la version la plus récente de ce script, indicateur -v signifie impression VERBOSE.
Sauvegardez-le dans un fichier nommé "cli-5.sh", rendez-le exécutable, et l'un d'entre eux fonctionnera ou échouera de la manière souhaité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 résultat de la vérification 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 d'activer -v, car il affiche les éléments internes d'OPTIND et d'OPTARG.
#/usr/bin/env bash
## Paul Johnson
## 20171016
##
## Combines ideas from
## https://stackoverflow.com/questions/402377/using-getopts-in-bash-Shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035
# What I don't understand yet:
# In @Arvid REquate's answer, we have
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!
die() {
printf '%s\n' "$1" >&2
exit 1
}
printparse(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
fi
}
showme(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'VERBOSE: %s\n' "$1" >&2;
fi
}
VERBOSE=0
loglevel=0
toc="TRUE"
optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do
showme "OPTARG: ${OPTARG[*]}"
showme "OPTIND: ${OPTIND[*]}"
case "${OPTCHAR}" in
-)
case "${OPTARG}" in
loglevel) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
printparse "--${OPTARG}" " " "${val}"
loglevel="${val}"
shift
;;
loglevel=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
printparse "--${opt}" "=" "${val}"
loglevel="${val}"
## shift CAUTION don't shift this, fails othewise
else
die "ERROR: $opt value must be supplied"
fi
;;
toc) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) #??
printparse "--${opt}" " " "${val}"
toc="${val}"
shift
;;
toc=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
toc=${val}
printparse "--$opt" " -> " "$toc"
##shift ## NO! dont shift this
else
die "ERROR: value for $opt undefined"
fi
;;
help)
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
h|-\?|--help)
## must rewrite this for all of the arguments
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
l)
loglevel=${OPTARG}
printparse "-l" " " "${loglevel}"
;;
t)
toc=${OPTARG}
;;
v)
VERBOSE=1
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
echo "
After Parsing values
"
echo "loglevel $loglevel"
echo "toc $toc"
Vous trouverez ici quelques approches différentes pour l’analyse complexe d’options dans bash: http://mywiki.wooledge.org/ComplexOptionParsing
J'ai créé le suivant, et je pense que c'est un bon choix, car c'est un code minimal Et les options longues et courtes fonctionnent. Une option longue peut également 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: $0 [--loglevel[=]<value>]" >&2
exit 2
;;
esac
break; done
done
# End of file
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 "$1" in
--)
# [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
EndOpt=1 ;;&
--version) ((EndOpt)) && args[$i]="$1" || args[$i]="-V";;
# default case : short option use the first char of the long option:
--?*) ((EndOpt)) && args[$i]="$1" || args[$i]="-${1:2:1}";;
# pass through anything else:
*) args[$i]="$1" ;;
esac
shift
done
# reset the translated args
set -- "${args[@]}"
function usage {
echo "Usage: $0 [options] files" >&2
exit $1
}
# now we can process with getopt
while getopts ":hvVc:" opt; do
case $opt in
h) usage ;;
v) VERBOSE=true ;;
V) echo $Version ; exit ;;
c) source $OPTARG ;;
\?) echo "unrecognized option: -$opt" ; usage -1 ;;
:)
echo "option -$OPTARG requires an argument"
usage -1
;;
esac
done
shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
Peut-être qu'il est plus simple d'utiliser ksh, juste pour la partie getopts, si besoin de longues options en ligne de commande, car cela peut être plus facile à faire là-bas.
# 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
Je voulais quelque chose sans dépendances externes, avec un support strict de bash (-u), et j'avais besoin de ça pour fonctionner même avec les versions plus anciennes de bash. Cela gère différents types de paramètres:
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 $1 ; do
local variants=${param//\|/ }
for variant in $variants ; do
if [[ "$variant" = "$2" ]] ; then
# Update the key to match the long version
local arr=(${param//\|/ })
let last=${#arr[@]}-1
key="${arr[$last]}"
return 0
fi
done
done
return 1
}
# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
# # First, set your defaults
# param_help=false
# param_path="."
# param_file=false
# param_image=false
# param_image_lossy=true
# # Define allowed parameters
# allowed_params="h|?|help p|path f|file i|image image-lossy"
# # Get parameters from the arguments provided
# _get_params $*
#
# Parameters will be converted into safe variable names like:
# param_help,
# param_path,
# param_file,
# param_image,
# param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
# -i "path/goes/here"
# --image "path/goes/here"
# --image="path/goes/here"
# --image=path/goes/here
# These would all result in effectively the same thing:
# param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
# -vhm is the same as -v -h -m
_get_params(){
local param_pair
local key
local value
local shift_count
while : ; do
# Ensure we have a valid param. Allows this to work even in -u mode.
if [[ $# == 0 || -z $1 ]] ; then
break
fi
# Split the argument if it contains "="
param_pair=(${1//=/ })
# Remove preceeding dashes
key="${param_pair[0]#--}"
# Check for concatinated boolean short parameters.
local nodash="${key#-}"
local breakout=false
if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
# Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
local short_param_count=${#nodash}
let new_arg_count=$#+$short_param_count-1
local new_args=""
# $str_pos is the current position in the short param string $nodash
for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
# The first character becomes the current key
if [ $str_pos -eq 0 ] ; then
key="${nodash:$str_pos:1}"
breakout=true
fi
# $arg_pos is the current position in the constructed arguments list
let arg_pos=$str_pos+1
if [ $arg_pos -gt $short_param_count ] ; then
# handle other arguments
let orignal_arg_number=$arg_pos-$short_param_count+1
local new_arg="${!orignal_arg_number}"
else
# break out our one argument into new ones
local new_arg="-${nodash:$str_pos:1}"
fi
new_args="$new_args \"$new_arg\""
done
# remove the preceding space and set the new arguments
eval set -- "${new_args# }"
fi
if ! $breakout ; then
key="$nodash"
fi
# By default we expect to shift one argument at a time
shift_count=1
if [ "${#param_pair[@]}" -gt "1" ] ; then
# This is a param with equals notation
value="${param_pair[1]}"
else
# This is either a boolean param and there is no value,
# or the value is the next command line argument
# Assume the value is a boolean true, unless the next argument is found to be a value.
value=true
if [[ $# -gt 1 && -n "$2" ]]; then
local nodash="${2#-}"
if [ "$nodash" = "$2" ]; then
# The next argument has NO preceding dash so it is a value
value="$2"
shift_count=2
fi
fi
fi
# Check that the param being passed is one of the allowed params
if _param_variant "$allowed_params" "$key" ; then
# --key-name will now become param_key_name
eval param_${key//-/_}="$value"
else
printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
fi
shift $shift_count
done
}
Et utilisez-le 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 $*
Je travaille sur ce sujet depuis assez longtemps ... et j'ai créé ma propre bibliothèque dont vous aurez besoin de trouver une source dans votre script principal . Voir libopt4Shell et cd2mpc pour un exemple. J'espère que ça aide !
Je n'ai pas encore assez de représentants pour commenter ou voter sa solution, mais la réponse de pme a extrêmement bien fonctionné pour moi. Le seul problème que j'ai rencontré est que les arguments finissent par être entourés de guillemets simples (je les ai donc supprimés).
J'ai également ajouté des exemples d'utilisation et du texte HELP. Je vais inclure ma version légèrement étendue ici:
#!/bin/bash
# getopt example
# from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-Shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
" USAGE:\n
Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n
Accepts the following forms:\n\n
getopt-example.sh -a -b -c value-for-c some-arg\n
getopt-example.sh -c value-for-c -a -b some-arg\n
getopt-example.sh -abc some-arg\n
getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
getopt-example.sh some-arg --clong value-for-c\n
getopt-example.sh
"
aflag=false
bflag=false
cargument=""
# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
-a|--along) aflag=true ;;
-b|--blong) bflag=true ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="$2" ; shift;;
-h|--help|-\?) echo -e $HELP_TEXT; exit;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g" (just leading/trailing) or | sed -e "s/'//g" (all)
echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}
while [ $# -gt 0 ]
do
echo arg=$1
shift
if [[ $aflag == true ]]; then
echo a is true
fi
done
La réponse acceptée fait un très beau travail en soulignant toutes les faiblesses de bash getopts
. La réponse se termine par:
Ainsi, s’il est possible d’écrire plus de code pour pallier le manque de prise en charge des options longues, c’est beaucoup plus de travail et rend partiellement caduque l’utilisation d’un analyseur getopt pour simplifier votre code.
Et même si je souscris en principe à cette affirmation, j'estime que le nombre de fois où nous avons tous implémenté cette fonctionnalité dans différents scripts justifie de déployer des efforts considérables pour créer une solution "normalisée" et bien testée.
En tant que tel, j'ai "mis à jour" bash construit dans getopts
en implémentant getopts_long
en pur bash, sans dépendance externe. L'utilisation de la fonction est compatible à 100% avec la variable getopts
intégrée.
En incluant getopts_long
(hébergé sur GitHub à l’adresse https://github.com/UmkaDK/getopts_long ) dans un script, la réponse à la question initiale peut être mise en œuvre aussi simplement que:
source "${PATH_TO}/getopts_long.bash"
while getopts_long ':c: copyfile:' OPTKEY; do
case ${OPTKEY} in
'c'|'copyfile')
echo 'file supplied -- ${OPTARG}'
;;
'?')
echo "INVALID OPTION -- ${OPTARG}" >&2
exit 1
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
*)
echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
exit 1
;;
esac
done
shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
La commande getopt intégrée à OS X (BSD) ne prend pas en charge les options longues, mais la version GNU le fait: brew install gnu-getopt
. Ensuite, quelque chose de similaire à: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt
.
Un simple bricolage pour obtenir uniquement des arguments nommés longs:
Utilisation:
$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""
Scénario:
#!/bin/bash
function main() {
ARGS=`getArgs "$@"`
a1=`echo "$ARGS" | getNamedArg a1`
a2=`echo "$ARGS" | getNamedArg a2`
a3=`echo "$ARGS" | getNamedArg a3`
a4=`echo "$ARGS" | getNamedArg a4`
a5=`echo "$ARGS" | getNamedArg a5`
a6=`echo "$ARGS" | getNamedArg a6`
a7=`echo "$ARGS" | getNamedArg a7`
echo "a1 = \"$a1\""
echo "a2 = \"$a2\""
echo "a3 = \"$a3\""
echo "a4 = \"$a4\""
echo "a5 = \"$a5\""
echo "a6 = \"$a6\""
echo "a7 = \"$a7\""
exit 0
}
function getArgs() {
for arg in "$@"; do
echo "$arg"
done
}
function getNamedArg() {
ARG_NAME=$1
sed --regexp-extended --quiet --expression="
s/^--$ARG_NAME=(.*)\$/\1/p # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
/^--$ARG_NAME\$/ { # Get arguments in format '--arg value' ou '--arg'
n # - [n]ext, because in this format, if value exists, it will be the next argument
/^--/! p # - If next doesn't starts with '--', it is the value of the actual argument
/^--/ { # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
# Then just repla[c]ed by TRUE
c TRUE
}
}
"
}
main "$@"
Afin de rester compatible entre plates-formes et d'éviter de dépendre des exécutables externes, j'ai transféré du code provenant d'une autre langue.
Je trouve cela 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 )"
BASH requis est un peu plus long que prévu, mais je voulais éviter de dépendre des tableaux associatifs de BASH 4. Vous pouvez également le télécharger directement depuis 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 "$0" is missing parameters
return 1
}
local delimiter="$1"
local string="$2"
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 "$1" && upvar $1 "value(s)"
upvar() {
if unset -v "$1"; then # Unset & validate varname
if (( $# == 2 )); then
eval $1=\"\$2\" # Return single value
else
eval $1=\(\"\${@: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 [ "${1}" == "${EXPLODED[0]}" ]
then
decho "Matched $1 with ${EXPLODED[0]}"
matched=1
break
fi
else
if [ "${1}" == "${EXPLODED[1]}" ]
then
decho "Matched $1 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=$3
value="${1:-true}"
declare -g __argpassed__$key="$value"
}
function ArgParser::parse
{
unset __argparser__argv
__argparser__argv=()
# echo parsing: "$@"
while [ -n "$1" ]
do
# echo "Processing $1"
if [ "${1:0:2}" == '--' ]
then
key=${1:2}
value=$2
Elif [ "${1:0:1}" == '-' ]
then
key=${1:1} # Strip off leading -
value=$2
else
decho "Not argument or option: '$1'" >& 2
__argparser__argv+=( "$1" )
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 '$1'" >&2 # && __argparser__argv+=( "$1" )
[ $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'" >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
shift
done
}
function ArgParser::isset
{
declare -p "__argpassed__$1" > /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__$1"
echo "${!varname}"
}
##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
local __varname="__argpassed__$1"
local __value="${!__varname}"
test -z "$__value" && return 1
local "$3" && upvar $3 "$__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 [[ "$1" =~ \[(.)\] ]]
then
short=${BASH_REMATCH[1]}
long=${1/\[$short\]/$short}
else
long=$1
fi
if [ "${#long}" -eq 1 ]
then
short=$long
long=''
fi
decho short: "$short"
decho long: "$long"
__argparser__arglist+=("$short|$long|$1|$2|$3")
}
##
# @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 "$0" )" == "argparser.inc.sh" ]
then
ArgParser::test "$@"
fi
EasyOptions gère les options courtes et longues:
## 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
Builtin getopts
analyse uniquement les options courtes (sauf dans ksh93), Mais vous pouvez toujours ajouter quelques lignes de script pour que getopts gère les options longues.
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
Sinon, dans Korn Shell ksh93, getopts
peut analyser naturellement de longues options et même afficher une page de manuel de la même manière. (Voir http://www.uxora.com/unix/Shell-script/20-getopts-with-man-page-and-long-options )
Si toutes vos options longues ont des premiers caractères uniques et correspondants comme options courtes, alors par exemple
./slamm --chaos 23 --plenty test -quiet
Est le même que
./slamm -c 23 -p test -q
Vous pouvez utiliser ceci before 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 à mtvee pour l'inspiration ;-)
si c'est comme cela que vous voulez appeler le script
myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"
alors vous pouvez suivre ce moyen le plus simple pour y parvenir à l'aide de getopt et --longoptions
essayez ceci, espérons que cela est utile
# Read command line options
ARGUMENT_LIST=(
"input1"
"input2"
"input3"
)
# read arguments
opts=$(getopt \
--longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$@"
)
echo $opts
eval set --$opts
while true; do
case "$1" in
--input1)
shift
empId=$1
;;
--input2)
shift
fromDate=$1
;;
--input3)
shift
toDate=$1
;;
--)
shift
break
;;
esac
shift
done