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