487 lines
16 KiB
Bash
487 lines
16 KiB
Bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
##@cooked nocomments
|
|
module: base.args "Fonctions de base: analyse d'arguments"
|
|
|
|
function: local_args "Afficher des commandes pour rendre locales des variables utilisées par parse_args()
|
|
|
|
Cela permet d'utiliser parse_args() à l'intérieur d'une fonction."
|
|
function local_args() {
|
|
echo "local -a args"
|
|
echo "local NULIB_ARGS_ONERROR_RETURN=1"
|
|
echo "local NULIB_VERBOSITY=\"\$NULIB_VERBOSITY\""
|
|
echo "local NULIB_INTERACTION=\"\$NULIB_INTERACTION\""
|
|
}
|
|
|
|
function: parse_args "Analyser les arguments de la ligne de commande à partir des définitions du tableau args
|
|
|
|
Cette fonction s'utilise ainsi:
|
|
~~~"'
|
|
args=(
|
|
[desc]
|
|
[usage]
|
|
[+|-]
|
|
-o,--longopt action [optdesc]
|
|
-a:,--mandarg: action [optdesc]
|
|
-b::,--optarg:: action [optdesc]
|
|
)
|
|
parse_args "$@"; set -- "${args[@]}"
|
|
~~~'"
|
|
|
|
au retour de la fonction, args contient les arguments qui n'ont pas été traités
|
|
automatiquement.
|
|
|
|
les options --help et --help++ sont automatiquement gérées. avec --help, seules
|
|
les options standards sont affichées. --help++ affiche toutes les options. les
|
|
descriptions sont utilisées pour l'affichage de l'aide. une option avancée est
|
|
identifiée par une description qui commence par ++
|
|
|
|
desc
|
|
: description de l'objet du script ou de la fonction. cette valeur est
|
|
facultative
|
|
|
|
usage
|
|
: description des arguments du script, sans le nom du script. par exemple la
|
|
valeur '[options] FILE' générera le texte d'aide suivant:
|
|
~~~
|
|
USAGE
|
|
\$MYNAME [options] FILE
|
|
~~~
|
|
Peut contenir autant de lignes que nécessaire. Chaque ligne est préfixée du
|
|
nom du script, jusqu'à la première ligne vide. Ensuite, les lignes sont
|
|
affichées telles quelles.
|
|
Le premier espace est ignoré, ce qui permet de spécifier des USAGEs avec une
|
|
option, e.g ' -c VALUE'
|
|
|
|
+|-
|
|
: méthode d'analyse des arguments.
|
|
* Par défaut, les options sont valides n'importe où sur la ligne de commande.
|
|
* Avec '+', l'analyse s'arrête au premier argument qui n'est pas une option.
|
|
* Avec '-', les options sont valides n'importe où sur la ligne de commande,
|
|
mais les arguments ne sont pas réordonnés, et apparaissent dans l'ordre de
|
|
leur mention. IMPORTANT: dans ce cas, aucun argument ni option n'est traité,
|
|
c'est à la charge de l'utilisateur. Au retour de la fonction, args contient
|
|
l'ensemble des arguments tels qu'analysés par getopt
|
|
|
|
-o, --longopt
|
|
: option sans argument
|
|
|
|
-a:, --mandarg:
|
|
: option avec argument obligatoire
|
|
|
|
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
|
|
-a:file pour un fichier. cette valeur est mise en majuscule lors de
|
|
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
|
|
|
|
-b::, --optarg::
|
|
: option avec argument facultatif
|
|
|
|
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
|
|
-b::file pour un fichier. cette valeur est mise en majuscule lors de
|
|
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
|
|
|
|
action
|
|
: action à effectuer si cette option est utilisée. plusieurs syntaxes sont valides:
|
|
* 'NAME' met à jour la variable en fonction du type d'argument: l'incrémenter
|
|
pour une option sans argument, lui donner la valeur spécifiée pour une
|
|
option avec argument, ajouter la valeur spécifiée au tableau si l'option est
|
|
spécifiée plusieurs fois.
|
|
la valeur spéciale '.' calcule une valeur de NAME en fonction du nom de
|
|
l'option la plus longue. par exemple, les deux définitions suivantes sont
|
|
équivalentes:
|
|
~~~
|
|
-o,--short,--very-long .
|
|
-o,--short,--very-long very_long
|
|
~~~
|
|
De plus, la valeur spéciale '.' traite les options de la forme --no-opt
|
|
comme l'inverse des option --opt. par exemple, les deux définitions
|
|
suivantes sont équivalentes:
|
|
~~~
|
|
--opt . --no-opt .
|
|
--opt opt --no-opt '\$dec@ opt'
|
|
~~~
|
|
* 'NAME=VALUE' pour une option sans argument, forcer la valeur spécifiée; pour
|
|
une option avec argument, prendre la valeur spécifiée comme valeur par
|
|
défaut si la valeur de l'option est vide
|
|
* '\$CMD' CMD est évalué avec eval *dès* que l'option est rencontrée.
|
|
les valeurs suivantes sont initialisées:
|
|
* option_ est l'option utilisée, e.g --long-opt
|
|
* value_ est la valeur de l'option
|
|
les fonctions suivantes sont définies:
|
|
* 'inc@ NAME' incrémente la variable NAME -- c'est le comportement de set@ si
|
|
l'option est sans argument
|
|
* 'dec@ NAME' décrémente la variable NAME, et la rend vide quand le compte
|
|
arrive à zéro
|
|
* 'res@ NAME VALUE' initialise la variable NAME avec la valeur de l'option (ou
|
|
VALUE si la valeur de l'option est vide) -- c'est le comportement de set@
|
|
si l'option prend un argument
|
|
* 'add@ NAME VALUE' ajoute la valeur de l'option (ou VALUE si la valeur de
|
|
l'option est vide) au tableau NAME.
|
|
* 'set@ NAME' met à jour la variable NAME en fonction de la définition de
|
|
l'option (avec ou sans argument, ajout ou non à un tableau)
|
|
|
|
optdesc
|
|
: description de l'option. cette valeur est facultative. si la description
|
|
commence par ++, c'est une option avancée qui n'est pas affichée par défaut."
|
|
function parse_args() {
|
|
eval "$NULIB__DISABLE_SET_X"
|
|
local __r=
|
|
local __DIE='[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && return 1 || die'
|
|
|
|
if ! is_array args; then
|
|
eerror "Invalid args definition: args must be defined"
|
|
__r=1
|
|
fi
|
|
# distinguer les descriptions des définition d'arguments
|
|
local __USAGE __DESC
|
|
local -a __DEFS __ARGS
|
|
__ARGS=("$@")
|
|
set -- "${args[@]}"
|
|
[ "${1#-}" == "$1" ] && { __DESC="$1"; shift; }
|
|
[ "${1#-}" == "$1" ] && { __USAGE="$1"; shift; }
|
|
if [ -n "$__r" ]; then
|
|
:
|
|
elif [ $# -gt 0 -a "$1" != "+" -a "${1#-}" == "$1" ]; then
|
|
eerror "Invalid args definition: third arg must be an option"
|
|
__r=1
|
|
else
|
|
__DEFS=("$@")
|
|
__parse_args || __r=1
|
|
fi
|
|
eval "$NULIB__ENABLE_SET_X"
|
|
if [ -n "$__r" ]; then
|
|
eval "$__DIE"
|
|
fi
|
|
}
|
|
function __parse_args() {
|
|
## tout d'abord, construire la liste des options
|
|
local __AUTOH=1 __AUTOHELP=1 # faut-il gérer automatiquement l'affichage de l'aide?
|
|
local __AUTOD=1 __AUTODEBUG=1 # faut-il rajouter les options -D et --debug
|
|
local __ADVHELP # y a-t-il des options avancées?
|
|
local __popt __sopts __lopts
|
|
local -a __defs
|
|
set -- "${__DEFS[@]}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
+) __popt="$1"; shift; continue;;
|
|
-) __popt="$1"; shift; continue;;
|
|
-*) IFS=, read -a __defs <<<"$1"; shift;;
|
|
*) eerror "Invalid arg definition: expected option, got '$1'"; eval "$__DIE";;
|
|
esac
|
|
# est-ce que l'option prend un argument?
|
|
local __def __witharg __valdesc
|
|
__witharg=
|
|
for __def in "${__defs[@]}"; do
|
|
if [ "${__def%::*}" != "$__def" ]; then
|
|
[ "$__witharg" != : ] && __witharg=::
|
|
elif [ "${__def%:*}" != "$__def" ]; then
|
|
__witharg=:
|
|
fi
|
|
done
|
|
# définitions __sopts et __lopts
|
|
for __def in "${__defs[@]}"; do
|
|
__def="${__def%%:*}"
|
|
if [[ "$__def" == --* ]]; then
|
|
# --longopt
|
|
__def="${__def#--}"
|
|
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
|
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
|
# -o
|
|
__def="${__def#-}"
|
|
__sopts="$__sopts$__def$__witharg"
|
|
[ "$__def" == h ] && __AUTOH=
|
|
[ "$__def" == D ] && __AUTOD=
|
|
else
|
|
# -longopt ou longopt
|
|
__def="${__def#-}"
|
|
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
|
fi
|
|
[ "$__def" == help -o "$__def" == help++ ] && __AUTOHELP=
|
|
[ "$__def" == debug ] && __AUTODEBUG=
|
|
done
|
|
# sauter l'action
|
|
shift
|
|
# sauter la description le cas échéant
|
|
if [ "${1#-}" == "$1" ]; then
|
|
[ "${1#++}" != "$1" ] && __ADVHELP=1
|
|
shift
|
|
fi
|
|
done
|
|
|
|
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
|
|
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
|
|
[ -n "$__AUTOD" ] && __sopts="${__sopts}D"
|
|
[ -n "$__AUTODEBUG" ] && __lopts="$__lopts${__lopts:+,}debug"
|
|
|
|
__sopts="$__popt$__sopts"
|
|
local -a __getopt_args
|
|
__getopt_args=(-n "$MYNAME" ${__sopts:+-o "$__sopts"} ${__lopts:+-l "$__lopts"} -- "${__ARGS[@]}")
|
|
|
|
## puis analyser et normaliser les arguments
|
|
if args="$(getopt -q "${__getopt_args[@]}")"; then
|
|
eval "set -- $args"
|
|
else
|
|
# relancer pour avoir le message d'erreur
|
|
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
|
|
eval "$__DIE"
|
|
fi
|
|
|
|
## puis traiter les options
|
|
local __defname __resvalue __decvalue __defvalue __add __action option_ name_ value_
|
|
function inc@() {
|
|
eval "[ -n \"\$$1\" ] || let $1=0"
|
|
eval "let $1=$1+1"
|
|
}
|
|
function dec@() {
|
|
eval "[ -n \"\$$1\" ] && let $1=$1-1"
|
|
eval "[ \"\$$1\" == 0 ] && $1="
|
|
}
|
|
function res@() {
|
|
local __value="${value_:-$2}"
|
|
eval "$1=\"\$__value\""
|
|
}
|
|
function add@() {
|
|
local __value="${value_:-$2}"
|
|
eval "$1+=(\"\$__value\")"
|
|
}
|
|
function set@() {
|
|
if [ -n "$__resvalue" ]; then
|
|
res@ "$@"
|
|
elif [ -n "$__witharg" ]; then
|
|
if is_array "$1"; then
|
|
add@ "$@"
|
|
elif ! is_defined "$1"; then
|
|
# première occurrence: variable
|
|
res@ "$@"
|
|
else
|
|
# deuxième occurence: tableau
|
|
[ -z "${!1}" ] && eval "$1=()"
|
|
add@ "$@"
|
|
fi
|
|
elif [ -n "$__decvalue" ]; then
|
|
dec@ "$@"
|
|
else
|
|
inc@ "$@"
|
|
fi
|
|
}
|
|
function showhelp@() {
|
|
local help="$MYNAME" showadv="$1"
|
|
if [ -n "$__DESC" ]; then
|
|
help="$help: $__DESC"
|
|
fi
|
|
|
|
local first usage nl=$'\n'
|
|
local prefix=" $MYNAME "
|
|
local usages="${__USAGE# }"
|
|
[ -n "$usages" ] || usages="[options]"
|
|
help="$help
|
|
|
|
USAGE"
|
|
first=1
|
|
while [ -n "$usages" ]; do
|
|
usage="${usages%%$nl*}"
|
|
if [ "$usage" != "$usages" ]; then
|
|
usages="${usages#*$nl}"
|
|
else
|
|
usages=
|
|
fi
|
|
if [ -n "$first" ]; then
|
|
first=
|
|
[ -z "$usage" ] && continue
|
|
else
|
|
[ -z "$usage" ] && prefix=
|
|
fi
|
|
help="$help
|
|
$prefix$usage"
|
|
done
|
|
|
|
set -- "${__DEFS[@]}"
|
|
first=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
+) shift; continue;;
|
|
-) shift; continue;;
|
|
-*) IFS=, read -a __defs <<<"$1"; shift;;
|
|
esac
|
|
if [ -n "$first" ]; then
|
|
first=
|
|
help="$help${nl}${nl}OPTIONS"
|
|
if [ -n "$__AUTOHELP" -a -n "$__ADVHELP" ]; then
|
|
help="$help
|
|
--help++
|
|
Afficher l'aide avancée"
|
|
fi
|
|
fi
|
|
# est-ce que l'option prend un argument?
|
|
__witharg=
|
|
__valdesc=value
|
|
for __def in "${__defs[@]}"; do
|
|
if [ "${__def%::*}" != "$__def" ]; then
|
|
[ "$__witharg" != : ] && __witharg=::
|
|
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
|
|
elif [ "${__def%:*}" != "$__def" ]; then
|
|
__witharg=:
|
|
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
|
|
fi
|
|
done
|
|
# description de l'option
|
|
local first=1 thelp tdesc
|
|
for __def in "${__defs[@]}"; do
|
|
__def="${__def%%:*}"
|
|
if [[ "$__def" == --* ]]; then
|
|
: # --longopt
|
|
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
|
: # -o
|
|
else
|
|
# -longopt ou longopt
|
|
__def="--${__def#-}"
|
|
fi
|
|
if [ -n "$first" ]; then
|
|
first=
|
|
thelp="${nl} "
|
|
else
|
|
thelp="$thelp, "
|
|
fi
|
|
thelp="$thelp$__def"
|
|
done
|
|
[ -n "$__witharg" ] && thelp="$thelp ${__valdesc^^}"
|
|
# sauter l'action
|
|
shift
|
|
# prendre la description le cas échéant
|
|
if [ "${1#-}" == "$1" ]; then
|
|
tdesc="$1"
|
|
if [ "${tdesc#++}" != "$tdesc" ]; then
|
|
# option avancée
|
|
if [ -n "$showadv" ]; then
|
|
tdesc="${tdesc#++}"
|
|
else
|
|
thelp=
|
|
fi
|
|
fi
|
|
[ -n "$thelp" ] && thelp="$thelp${nl} ${tdesc//$nl/$nl }"
|
|
shift
|
|
fi
|
|
[ -n "$thelp" ] && help="$help$thelp"
|
|
done
|
|
uecho "$help"
|
|
exit 0
|
|
}
|
|
if [ "$__popt" != - ]; then
|
|
while [ $# -gt 0 ]; do
|
|
if [ "$1" == -- ]; then
|
|
shift
|
|
break
|
|
fi
|
|
[[ "$1" == -* ]] || break
|
|
option_="$1"; shift
|
|
__parse_opt "$option_"
|
|
if [ -n "$__witharg" ]; then
|
|
# l'option prend un argument
|
|
value_="$1"; shift
|
|
else
|
|
# l'option ne prend pas d'argument
|
|
value_=
|
|
fi
|
|
eval "$__action"
|
|
done
|
|
fi
|
|
unset -f inc@ res@ add@ set@ showhelp@
|
|
args=("$@")
|
|
}
|
|
function __parse_opt() {
|
|
# $1 est l'option spécifiée
|
|
local option_="$1"
|
|
set -- "${__DEFS[@]}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
+) shift; continue;;
|
|
-) shift; continue;;
|
|
-*) IFS=, read -a __defs <<<"$1"; shift;;
|
|
esac
|
|
# est-ce que l'option prend un argument?
|
|
__witharg=
|
|
for __def in "${__defs[@]}"; do
|
|
if [ "${__def%::*}" != "$__def" ]; then
|
|
[ "$__witharg" != : ] && __witharg=::
|
|
elif [ "${__def%:*}" != "$__def" ]; then
|
|
__witharg=:
|
|
fi
|
|
done
|
|
# nom le plus long
|
|
__defname=
|
|
local __found=
|
|
for __def in "${__defs[@]}"; do
|
|
__def="${__def%%:*}"
|
|
[ "$__def" == "$option_" ] && __found=1
|
|
if [[ "$__def" == --* ]]; then
|
|
# --longopt
|
|
__def="${__def#--}"
|
|
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
|
|
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
|
# -o
|
|
__def="${__def#-}"
|
|
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
|
|
else
|
|
# -longopt ou longopt
|
|
__def="${__def#-}"
|
|
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
|
|
fi
|
|
done
|
|
__defname="${__defname//-/_}"
|
|
# analyser l'action
|
|
__decvalue=
|
|
if [ "${1#\$}" != "$1" ]; then
|
|
name_="$__defname"
|
|
__resvalue=
|
|
__defvalue=
|
|
__action="${1#\$}"
|
|
else
|
|
if [ "$1" == . ]; then
|
|
name_="$__defname"
|
|
__resvalue=
|
|
__defvalue=
|
|
if [ "${name_#no_}" != "$name_" ]; then
|
|
name_="${name_#no_}"
|
|
__decvalue=1
|
|
fi
|
|
elif [[ "$1" == *=* ]]; then
|
|
name_="${1%%=*}"
|
|
__resvalue=1
|
|
__defvalue="${1#*=}"
|
|
else
|
|
name_="$1"
|
|
__resvalue=
|
|
__defvalue=
|
|
fi
|
|
__action="$(qvals set@ "$name_" "$__defvalue")"
|
|
fi
|
|
shift
|
|
# sauter la description le cas échéant
|
|
[ "${1#-}" == "$1" ] && shift
|
|
|
|
[ -n "$__found" ] && return 0
|
|
done
|
|
if [ -n "$__AUTOH" -a "$option_" == -h ]; then
|
|
__action="showhelp@"
|
|
return 0
|
|
fi
|
|
if [ -n "$__AUTOHELP" ]; then
|
|
if [ "$option_" == --help ]; then
|
|
__action="showhelp@"
|
|
return 0
|
|
elif [ "$option_" == --help++ ]; then
|
|
__action="showhelp@ ++"
|
|
return 0
|
|
fi
|
|
fi
|
|
if [ -n "$__AUTOD" -a "$option_" == -D ]; then
|
|
__action=set_debug
|
|
return 0
|
|
fi
|
|
if [ -n "$__AUTODEBUG" -a "$option_" == --debug ]; then
|
|
__action=set_debug
|
|
return 0
|
|
fi
|
|
# ici, l'option n'a pas été trouvée, on ne devrait pas arriver ici
|
|
eerror "Unexpected option '$option_'"; eval "$__DIE"
|
|
}
|