# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ##@cooked nocomments module: base.eval "Fonctions de base: évaluation d'expressions" require: base.str base.arr ################################################################################ # Chaines function: evals "Appliquer à une chaine de caractères une suite de traitements, e.g: ~~~ evals var deref +suffix ~~~ est équivalent à ~~~ echo \"\${var}suffix\" ~~~ En commençant avec la valeur initiale \$1, les arguments \$2..* sont des opérations à appliquer dans l'ordre. Les opérations suivantes considèrent que la valeur courante est un nom de variable: ~~~ :- := :? :+ deref dcount ~~~ Toutes les autres opérations travaillent directement avec la valeur courante. Les opérations suivantes appliquent une transformation: ~~~ # % / : ^ , +# -# +% -% + - mid repl ~~~ IMPORTANT: aucune de ces fonctions ne met en échappement les valeur des patterns. Ainsi, si un pattern contient des caractères interdits comme \\ ou \$, il faut d'abord le traiter avec _qval() Les opérations suivantes font un test sur la valeur et retournent immédiatement: ~~~ = == != < > -eq -ne -lt -le -gt -ge -n -z ~~~ La syntaxe des opérateurs standards de bash est reprise autant que possible, i.e si on a l'habitude d'écrire ${varOP} en bash, alors la syntaxe à utiliser à priori est 'evals var OP' ou 'evals var deref OP' suivant les opérateurs. Autres opérateurs: ~~~ deref indirection dcount nombre d'éléments du tableau +#STR ajouter un préfixe -#STR supprimer un préfixe +%STR ou +STR ajouter un suffixe -%STR ou -STR supprimer un suffixe mid RANGE traiter la chaine avec strmid() repl FROM TO traiter la chaine avec strrepl() ~~~ Tout autre opérateur est traité comme un appel à une fonction qui prend un seul argument, la valeur courante, et qui affiche le résultat." function evals() { local -a es__tmp local es__value="$1"; shift while [ $# -gt 0 ]; do case "$1" in # l'argument est le nom de la variable :-*|:=*|:\?*|:+*) eval 'es__value="${'"${es__value}$1"'}"';; d|deref) es__value="${!es__value}";; dc|dcount|ds|dsize) es__value="${es__value}[@]" es__tmp=("${!es__value}") es__value="${#es__tmp[@]}" ;; # l'argument est la valeur de la variable \#*|%*|/*|:*|^*|,*) eval 'es__value="${es__value'"$1"'}"';; l|length) es__value="${#es__value}";; =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) es__tmp=(\[ "$es__value" "$@" ]); "${es__tmp[@]}"; return $?;; -n|-z) es__tmp=(\[ "$1" "$es__value" ]); "${es__tmp[@]}"; return $?;; +#*) eval 'es__value="'"${1#+#}"'$es__value"';; -#*) eval 'es__value="${es__value'"${1#-}"'}"';; +%*) eval 'es__value="$es__value"'"${1#+%}";; +*) eval 'es__value="$es__value"'"${1#+}";; -%*) eval 'es__value="${es__value'"${1#-}"'}"';; -*) eval 'es__value="${es__value%'"${1#-}"'}"';; mid|strmid) eval 'es__value="$(strmid "$2" "$es__value")"'; shift;; repl|strrepl) eval 'es__value="$(strrepl "$2" "$3" "$es__value")"'; shift; shift;; *) es__value="$("$1" "$es__value")";; esac shift done echo "$es__value" } function: setxs "équivalent à setx \$1 evals \$2..@" function setxs() { local -a ss__args if [ "$1" == -a ]; then ss__args=(-a); shift; fi local ss__var="$1"; shift if [[ "$ss__var" == *=* ]]; then set -- "${ss__var#*=}" "$@" ss__var="${ss__var%%=*}" fi ss__args=("${ss__args[@]}" "$ss__var") setx "${ss__args[@]}" evals "$@" } function: cmds "lancer une commande avec comme argument le résultat de evals Par exemple, les deux commandes suivantes sont équivalentes: ~~~ cmds CMD ARGS... // EVALARGS CMD ARGS... \"\$(evals EVALARGS)\" ~~~" function cmds() { local cs__arg local -a cs__cmd while [ $# -gt 0 ]; do cs__arg="$1"; shift [ "$cs__arg" == // ] && break cs__cmd=("${cs__cmd[@]}" "$cs__arg") done "${cs__cmd[@]}" "$(evals "$@")" } function: evalm "construire une chaine en mixant chaines statiques et évaluations de commandes Par exemple, les deux commandes suivantes sont équivalentes: ~~~ evalm //\"string\" cmd args // cmd args //\"string\" echo \"string\$(cmd args)\$(cmd args)string\" ~~~" function evalm() { local em__val em__arg local -a em__cmd while [ $# -gt 0 ]; do em__arg="$1" if [ "${em__arg#//}" != "$em__arg" ]; then em__val="$em__val${em__arg#//}" shift continue fi em__cmd=() while [ $# -gt 0 ]; do em__arg="$1" [ "${em__arg#//}" != "$em__arg" ] && break shift if [ "${em__arg%//}" != "$em__arg" ]; then local em__tmp="${em__arg%//}" if [ -z "${em__tmp//\\/}" ]; then em__arg="${em__arg#\\}" em__cmd=("${em__cmd[@]}" "$em__arg") continue fi fi em__cmd=("${em__cmd[@]}" "$em__arg") done [ ${#em__cmd[*]} -gt 0 ] && em__val="$em__val$("${em__cmd[@]}")" done echo "$em__val" } function: setxm "équivalent à setx \$1 evalm \$2..@" function setxm() { local -a sm__args if [ "$1" == -a ]; then sm__args=(-a); shift; fi local sm__var="$1"; shift if [[ "$sm__var" == *=* ]]; then set -- "${sm__var#*=}" "$@" sm__var="${sm__var%%=*}" fi sm__args=("${sm__args[@]}" "$sm__var") setx "${sm__args[@]}" evalm "$@" } function: cmdm "lancer une commande avec comme argument le résultat de evalm Par exemple, les deux commandes suivantes sont équivalentes: ~~~ cmdm CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalm EVALARGS)\" ~~~" function cmdm() { local cm__arg local -a cm__cmd while [ $# -gt 0 ]; do cm__arg="$1"; shift [ "$cm__arg" == // ] && break cm__cmd=("${cm__cmd[@]}" "$cm__arg") done "${cm__cmd[@]}" "$(evalm "$@")" } ################################################################################ # Nombres function: evali "Evaluer une expression numérique" function evali() { echo "$(($*))" } ################################################################################ # Tableaux ################################################################################ # Composition function: evalc "Implémenter une syntaxe lisible et naturelle permettant d'enchainer des traitements sur une valeur. Par exemple, la commande ~~~ evalc cmd1... // cmd2... // cmd3... ~~~ est équivalente à la commande ~~~ cmd3... \"\$(cmd2... \"\$(cmd1...)\")\" ~~~" function evalc() { local ec__arg ec__cmd ec__finalcmd while [ $# -gt 0 ]; do ec__arg="$1"; shift if [ "$ec__arg" == // ]; then if [ ${#ec__cmd} -gt 0 ]; then if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd" else ec__finalcmd="$ec__cmd \$($ec__finalcmd)" fi fi ec__cmd= continue elif [ "${ec__arg%//}" != "$ec__arg" ]; then local tmp="${ec__arg%//}" [ -z "${tmp//\\/}" ] && ec__arg="${ec__arg#\\}" fi ec__cmd="$ec__cmd \"$(_qval "$ec__arg")\"" done if [ ${#ec__cmd} -gt 0 ]; then if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd" else ec__finalcmd="$ec__cmd \$($ec__finalcmd)" fi fi eval "$ec__finalcmd" } function: setxc "équivalent à setx \$1 evalc \$2..@" function setxc() { local -a sx__args if [ "$1" == -a ]; then sx__args=(-a); shift; fi local sx__var="$1"; shift if [[ "$sx__var" == *=* ]]; then set -- "${sx__var#*=}" "$@" sx__var="${sx__var%%=*}" fi sx__args=("${sx__args[@]}" "$sx__var") setx "${sx__args[@]}" evalc "$@" } ################################################################################ # Chainage function: evalp "Implémenter une syntaxe alternative permettant d'enchainer des traitements sur un flux de données. Par exemple, la commande ~~~ evalp cmd1... // cmd2... // cmd3... ~~~ affiche le résultat de la commande ~~~ cmd1... | cmd2... | cmd3... ~~~ Typiquement, cette fonction permet de faciliter la *construction* d'un enchainement de commandes par programme, ou de faciliter l'utilisation de la fonction setx() pour récupérer le résultat d'un enchainement. Dans les autres cas, il est plus simple et naturel d'écrire les enchainements avec la syntaxe de bash." function evalp() { local ep__arg ep__cmd while [ $# -gt 0 ]; do ep__arg="$1"; shift if [ "$ep__arg" == // ]; then ep__cmd="$ep__cmd |" continue elif [ "${ep__arg%//}" != "$ep__arg" ]; then local ep__tmp="${ep__arg%//}" if [ -z "${ep__tmp//\\/}" ]; then ep__arg="${ep__arg#\\}" fi fi ep__cmd="${ep__cmd:+$ep__cmd }\"$(_qval "$ep__arg")\"" done eval "$ep__cmd" } function: setxp "équivalent à setx \$1 evalp \$2..@" function setxp() { local -a sp__args if [ "$1" == -a ]; then sp__args=(-a); shift; fi local sp__var="$1"; shift if [[ "$sp__var" == *=* ]]; then set -- "${sp__var#*=}" "$@" sp__var="${sp__var%%=*}" fi sp__args=("${sp__args[@]}" "$sp__var") setx "${sp__args[@]}" evalp "$@" } function: cmdp "lancer une commande avec comme argument le résultat de evalp Par exemple, les deux commandes suivantes sont équivalentes: ~~~ cmdp CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalp EVALARGS)\" ~~~" function cmdp() { local cp__arg local -a cp__cmd while [ $# -gt 0 ]; do cp__arg="$1"; shift [ "$cp__arg" == // ] && break cp__cmd=("${cp__cmd[@]}" "$cp__arg") done "${cp__cmd[@]}" "$(evalp "$@")" } ################################################################################ # Générique function: evalx "" function evalx() { : } function: setxx "équivalent à setx \$1 evalx \$2..@" function setxx() { local -a sx__args if [ "$1" == -a ]; then sx__args=(-a); shift; fi local sx__var="$1"; shift if [[ "$sx__var" == *=* ]]; then set -- "${sx__var#*=}" "$@" sx__var="${sx__var%%=*}" fi sx__args=("${sx__args[@]}" "$sx__var") setx "${sx__args[@]}" evalx "$@" } function: cmdx "lancer une commande avec comme argument le résultat de evalx Par exemple, les deux commandes suivantes sont équivalentes: ~~~ cmdx CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalx EVALARGS)\" ~~~" function cmdx() { local cx__arg local -a cx__cmd while [ $# -gt 0 ]; do cx__arg="$1"; shift [ "$cx__arg" == // ] && break cx__cmd=("${cx__cmd[@]}" "$cx__arg") done "${cx__cmd[@]}" "$(evalx "$@")" } function: cmdsplitf "\ Cette fonction doit être appelée avec N arguments (avec N>1). Elle analyse et découpe l'argument \$N comme avec une ligne de commande du shell. Ensuite, elle appelle la fonction \$1 avec les arguments de \$2 à \${N-1}, suivi des arguments obtenus lors de l'analyse de l'argument \$N. Par exemple, la commande suivante: ~~~ strsplitf cmd arg1 \"long arg2\" \"arg3 'long arg4'\" ~~~ est équivalente à: ~~~ cmd arg1 \"long arg2\" arg3 \"long arg4\" ~~~ Retourner le code 127 si la fonction à appeler n'est pas spécifiée. Retourner le code 126 si une erreur s'est produite lors de l'analyse de l'argument \$N" function cmdsplitf() { [ $# -gt 0 ] || return 127 local func count func="$1"; shift count=$# if [ $count -gt 0 ]; then eval 'set -- "${@:1:$(($count-1))}" '"${!count}" || return 126 fi "$func" "$@" } ################################################################################ # Tests function: testx "Faire un test unaire avec la commande [ sur une valeur calculée avec evalx. Utiliser la syntaxe 'testx op cmds...' e.g. ~~~ testx -z cmd1 // cmd2 ~~~" function testx() { local t__op="$1"; shift local t__val="$(evalx "$@")" [ $t__op "$t__val" ] } function: test2x "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalx. Utiliser la syntaxe 'test2x value op cmds...' e.g. ~~~ test2x value == cmd1 // cmd2 ~~~" function test2x() { local t__val1="$1"; shift local t__op="$1"; shift local t__val2="$(evalx "$@")" [ "$t__val1" $t__op "$t__val2" ] } function: testrx "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalx. Utiliser la syntaxe 'testrx value op cmds...' e.g. ~~~ testrx value == cmd1 // cmd2 ~~~" function testrx() { local t__val1="$1"; shift local t__op="$1"; shift local t__val2="$(evalx "$@")" eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]' } function: testp "Faire un test unaire avec la commande [ sur une valeur calculée avec evalp. Utiliser la syntaxe 'testp op cmds...' e.g. ~~~ testp -z cmd1 // cmd2 ~~~" function testp() { local t__op="$1"; shift local t__val="$(evalp "$@")" [ $t__op "$t__val" ] } function: test2p "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalp. Utiliser la syntaxe 'test2p value op cmds...' e.g. ~~~ test2p value == cmd1 // cmd2 ~~~" function test2p() { local t__val1="$1"; shift local t__op="$1"; shift local t__val2="$(evalp "$@")" [ "$t__val1" $t__op "$t__val2" ] } function: testrp "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalp. Utiliser la syntaxe 'testrp value op cmds...' e.g. ~~~ testrp value == cmd1 // cmd2 ~~~" function testrp() { local t__val1="$1"; shift local t__op="$1"; shift local t__val2="$(evalp "$@")" eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]' }