# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ##@cooked nocomments module: base.eval base_ "Fonctions de base: évaluation d'expressions" require: base.str base.arr ################################################################################ # Chaines function: base_evals "Appliquer à une chaine de caractères une suite de traitements, e.g: ~~~ base_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 'base_evals var OP' ou 'base_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 base_strmid() repl FROM TO traiter la chaine avec base_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 base_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|base_strmid) eval 'es__value="$(base_strmid "$2" "$es__value")"'; shift;; repl|strrepl|base_strrepl) eval 'es__value="$(base_strrepl "$2" "$3" "$es__value")"'; shift; shift;; *) es__value="$("$1" "$es__value")";; esac shift done echo "$es__value" } function: base_setxs "équivalent à setx \$1 evals \$2..@" function base_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[@]}" base_evals "$@" } function: base_cmds "lancer une commande avec comme argument le résultat de evals Par exemple, les deux commandes suivantes sont équivalentes: ~~~ base_cmds CMD ARGS... // EVALARGS CMD ARGS... \"\$(evals EVALARGS)\" ~~~" function base_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[@]}" "$(base_evals "$@")" } function: base_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 base_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: base_setxm "équivalent à setx \$1 evalm \$2..@" function base_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[@]}" base_evalm "$@" } function: base_cmdm "lancer une commande avec comme argument le résultat de evalm Par exemple, les deux commandes suivantes sont équivalentes: ~~~ base_cmdm CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalm EVALARGS)\" ~~~" function base_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[@]}" "$(base_evalm "$@")" } ################################################################################ # Nombres function: base_evali "Evaluer une expression numérique" function base_evali() { echo "$(($*))" } ################################################################################ # Tableaux ################################################################################ # Composition function: base_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 base_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: base_setxc "équivalent à setx \$1 evalc \$2..@" function base_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[@]}" base_evalc "$@" } ################################################################################ # Chainage function: base_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 base_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: base_setxp "équivalent à setx \$1 evalp \$2..@" function base_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[@]}" base_evalp "$@" } function: base_cmdp "lancer une commande avec comme argument le résultat de evalp Par exemple, les deux commandes suivantes sont équivalentes: ~~~ base_cmdp CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalp EVALARGS)\" ~~~" function base_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[@]}" "$(base_evalp "$@")" } ################################################################################ # Générique function: base_evalx "" function base_evalx() { : } function: base_setxx "équivalent à setx \$1 evalx \$2..@" function base_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[@]}" base_evalx "$@" } function: base_cmdx "lancer une commande avec comme argument le résultat de evalx Par exemple, les deux commandes suivantes sont équivalentes: ~~~ base_cmdx CMD ARGS... // EVALARGS CMD ARGS... \"\$(evalx EVALARGS)\" ~~~" function base_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[@]}" "$(base_evalx "$@")" } function: base_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 base_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" ]]' }