##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ## Fonctions de base: fondement ##@cooked nocomments uprovide base.core function echo_() { # afficher la valeur $* sans passer à la ligne echo -n "$*" } function recho() { # afficher une valeur brute. contrairement à la commande echo, ne reconnaitre # aucune option (i.e. -e, -E, -n ne sont pas signifiants) if [[ "${1:0:2}" == -[eEn] ]]; then local first="${1:1}"; shift echo -n - echo "$first" "$@" else echo "$@" fi } function recho_() { # afficher une valeur brute, sans passer à la ligne. contrairement à la commande # echo, ne reconnaitre aucune option (i.e. -e, -E, -n ne sont pas signifiants) if [[ "${1:0:2}" == -[eEn] ]]; then local first="${1:1}"; shift echo -n - echo -n "$first" "$@" else echo -n "$@" fi } function _qval() { # Dans la chaine $*, remplacer \ par \\, " par \", $ par \$, ` par \` # Cela permet de quoter une chaine à mettre entre guillements. # Note: la protection de ! n'est pas effectuée, parce que le comportement du # shell est incohérent entre le shell interactif et les scripts. Pour une # version plus robuste, il est nécessaire d'utiliser un programme externe tel # que sed ou awk local s="$*" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//\$/\\\$}" s="${s//\`/\\\`}" recho_ "$s" } function should_quote() { # Tester si la chaine $* doit être mise entre quotes local s="$*" local l="${#s}" # pour optimiser, toujours mettre entre quotes une chaine vide ou de plus de 80 caractères [ $l -eq 0 -o $l -gt 80 ] && return 0 # sinon, tester si la chaine contient des caractères spéciaux s="${s//[a-zA-Z0-9]/}" s="${s//,/}" s="${s//./}" s="${s//+/}" s="${s//\//}" s="${s//-/}" s="${s//_/}" s="${s//=/}" [ -n "$s" ] } function qval() { # Afficher la chaine $* quotée avec " echo -n \" _qval "$@" echo \" } function qvalm() { # Afficher la chaine $* quotée si nécessaire avec " if should_quote "$*"; then echo -n \" _qval "$@" echo \" else recho "$*" fi } function qvalr() { # Afficher la chaine $* quotée si nécessaire avec ", sauf si elle est vide if [ -z "$*" ]; then : elif should_quote "$*"; then echo -n \" _qval "$@" echo \" else recho "$*" fi } function qvals() { # Afficher chaque argument de cette fonction quotée le cas échéant avec " # Chaque valeur est séparée par un espace. local arg first=1 for arg in "$@"; do [ -z "$first" ] && echo -n " " if should_quote "$arg"; then echo -n \" _qval "$arg" echo -n \" else recho_ "$arg" fi first= done [ -z "$first" ] && echo } function qwc() { # Dans la chaine $*, remplacer \ par \\, " par \", $ par \$, ` par \`, puis # quoter la chaine avec ", sauf les wildcards * et ? # Cela permet de quoter une chaine permettant de glober des fichiers, e.g # eval "ls $(qwc "$value")" # Note: la protection de ! n'est pas effectuée, parce que le comportement du # shell est incohérent entre le shell interactif et les scripts. Pour une # version plus robuste, il est nécessaire d'utiliser un programme externe tel # que sed ou awk local s="$*" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//\$/\\\$}" s="${s//\`/\\\`}" local r a b while [ -n "$s" ]; do if [[ "$s" == *\** ]]; then if [[ "$s" == *\?* ]]; then a="${s%%\**}" b="${s%%\?*}" if [ ${#a} -lt ${#b} ]; then s="${s#*\*}" r="$r\"$a\"*" else s="${s#*\?}" r="$r\"$b\"?" fi else a="${s%%\**}" s="${s#*\*}" r="$r\"$a\"*" fi elif [[ "$s" == *\?* ]]; then if [[ "$s" == *\** ]]; then a="${s%%\**}" b="${s%%\?*}" if [ ${#a} -lt ${#b} ]; then s="${s#*\*}" r="$r\"$a\"*" else s="${s#*\?}" r="$r\"$b\"?" fi else a="${s%%\?*}" s="${s#*\?}" r="$r\"$a\"?" fi else r="$r\"$s\"" break fi done recho_ "$r" } function qlines() { # Traiter chaque ligne de l'entrée standard pour en faire des chaines quotées # avec ' sed "s/'/'\\\\''/g; s/.*/'&'/g" } function setv() { # initialiser la variable $1 avec la valeur $2..* # note: en principe, la syntaxe est 'setv var values...'. cependant, la # syntaxe 'setv var=values...' est supportée aussi local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" fi eval "$__s_var=\"\$*\"" } function _setv() { # Comme la fonction setv() mais ne supporte que la syntaxe '_setv var values...' # Cette fonction est légèrement plus rapide que setv() local __s_var="$1"; shift eval "$__s_var=\"\$*\"" } function echo_setv() { # Afficher la commande qui serait lancée par setv "$@" local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" fi echo "$__s_var=$(qvalr "$*")" } function seta() { # initialiser le tableau $1 avec les valeurs $2..@ # note: en principe, la syntaxe est 'seta array values...'. cependant, la # syntaxe 'seta array=values...' est supportée aussi local __s_array="$1"; shift if [[ "$__s_array" == *=* ]]; then set -- "${__s_array#*=}" "$@" __s_array="${__s_array%%=*}" fi eval "$__s_array=(\"\$@\")" } function _seta() { # Comme la fonction seta() mais ne supporte que la syntaxe '_seta array values...' # Cette fonction est légèrement plus rapide que seta() local __s_array="$1"; shift eval "$__s_array=(\"\$@\")" } function echo_seta() { # Afficher la commande qui serait lancée par seta "$@" # Le cas particulier 'seta array' affiche la commande pour recréer le # tableau array local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" elif [ $# -eq 0 ]; then eval "set -- \"\${$__s_var[@]}\"" fi echo "$__s_var=($(qvals "$@"))" } function setx() { # syntaxe 1: setx var cmd # initialiser la variable $1 avec le résultat de la commande "$2..@" # note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la # syntaxe 'setx var=cmd args...' est supportée aussi # syntaxe 2: setx -a array cmd # initialiser le tableau $1 avec le résultat de la commande "$2..@", chaque # ligne du résultat étant un élément du tableau # note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la # syntaxe 'setx -a array=cmd args...' est supportée aussi if [ "$1" == -a ]; then shift local __s_array="$1"; shift if [[ "$__s_array" == *=* ]]; then set -- "${__s_array#*=}" "$@" __s_array="${__s_array%%=*}" fi eval "$__s_array=($("$@" | qlines))" else local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" fi eval "$__s_var="'"$("$@")"' fi } function _setvx() { # Comme la fonction setx() mais ne supporte que l'initialisation d'une variable # scalaire avec la syntaxe '_setvx var cmd args...' pour gagner (un peu) en # rapidité d'exécution. local __s_var="$1"; shift eval "$__s_var="'"$("$@")"' } function _setax() { # Comme la fonction setx() mais ne supporte que l'initialisation d'un tableau # avec la syntaxe '_setax array cmd args...' pour gagner (un peu) en rapidité # d'exécution. local __s_array="$1"; shift eval "$__s_array=($("$@" | qlines))" } function evalx() { # Implémenter une syntaxe lisible et naturelle permettant d'enchainer des # traitements sur une valeur. Par exemple, la commande # evalx cmd1 [args1...] // cmd2 [args2...] // cmd3 [args3...] # est équivalente à la commande # cmd3 args3 "$(cmd2 args2 "$(cmd1 args1)")" # Retourner le dernier code d'erreur non nul, ou 0 si toutes les commandes se # sont exécutées sans erreur. local __e_val __e_arg __e_r=0 local -a __e_cmd local __e_first=1 while [ $# -gt 0 ]; do __e_cmd=() while [ $# -gt 0 ]; do __e_arg="$1"; shift [ "$__e_arg" == // ] && break if [ "${__e_arg%//}" != "$__e_arg" ]; then local __e_tmp="${__e_arg%//}" if [ -z "${__e_tmp//\\/}" ]; then __e_arg="${__e_arg#\\}" __e_cmd=("${__e_cmd[@]}" "$__e_arg") continue fi fi __e_cmd=("${__e_cmd[@]}" "$__e_arg") done if [ -n "$__e_first" ]; then __e_val="$("${__e_cmd[@]}")" || __e_r=$? else __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$? fi __e_first= done [ -n "$__e_val" ] && echo "$__e_val" return $__e_r } function setxx() { # équivalent à setx $1 evalx $2..@ local -a __s_args if [ "$1" == -a ]; then __s_args=(-a); shift; fi local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" fi __s_args=("${__s_args[@]}" "$__s_var") setx "${__s_args[@]}" evalx "$@" } 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. local __e_arg __e_cmd while [ $# -gt 0 ]; do __e_arg="$1"; shift if [ "$__e_arg" == // ]; then __e_cmd="$__e_cmd |" continue elif [ "${__e_arg%//}" != "$__e_arg" ]; then local __e_tmp="${__e_arg%//}" if [ -z "${__e_tmp//\\/}" ]; then __e_arg="${__e_arg#\\}" fi fi __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\"" done eval "$__e_cmd" } function setxp() { # équivalent à setx $1 evalp $2..@ local -a __s_args if [ "$1" == -a ]; then __s_args=(-a); shift; fi local __s_var="$1"; shift if [[ "$__s_var" == *=* ]]; then set -- "${__s_var#*=}" "$@" __s_var="${__s_var%%=*}" fi __s_args=("${__s_args[@]}" "$__s_var") setx "${__s_args[@]}" evalp "$@" } 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 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 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 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 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 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 local __t_val1="$1"; shift local __t_op="$1"; shift local __t_val2="$(evalp "$@")" eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]' } function err2out() { # lancer la commande $@ en redirigeant la sortie d'erreur sur la sortie standard "$@" 2>&1 } function is_defined() { # tester si la variable $1 est définie [ -n "$(declare -p "$1" 2>/dev/null)" ] } function is_array() { # tester si la variable $1 est un tableau case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac return 1 } # cf http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference function upvar() { # Assign variable one scope above the caller # Usage: local "$1" && upvar $1 "value(s)" # Param: $1 Variable name to assign value to # Param: $* Value(s) to assign. If multiple values, an array is # assigned, otherwise a single value is assigned. if unset -v "$1"; then if [ $# -eq 2 ]; then eval "$1=\"\$2\"" else eval "$1=(\"\${@:2}\")" fi fi } function array_upvar() { # Comme upvar() mais force la création d'un tableau unset -v "$1" && eval "$1=(\"\${@:2}\")" } function upvars() { # Version modifiée par rapport à l'original. Il n'est plus nécessaire de # préfixer une variable scalaire avec -v, et -a peut être spécifié sans # argument. # Usage: # local varname [varname ...] && upvars [varname value] | [-aN varname [value ...]] ... # Options: # -a # assigns remaining values to varname as array # -aN # assigns next N values to varname as array. Returns 1 if wrong number of # options occurs while [ $# -gt 0 ]; do case "$1" in -a) unset -v "$2" && eval "$2=(\"\${@:3}\")" break ;; -a*) unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" shift $((${1#-a} + 2)) || return 1 ;; *) unset -v "$1" && eval "$1=\"\$2\"" shift; shift ;; esac done } function __ab_process_pending() { local -a values case "$mode" in cmd) values="$("${pending[@]}")";; ssplit) eval "values=($("${pending[@]}"))";; lsplit) eval "values=($("${pending[@]}" | qlines))";; add) values=("${pending[@]}");; esac cmd=("${cmd[@]}" "${values[@]}") pending=() } function array_buildcmd() { # Construire un tableau dont les éléments sont calculés à partir d'une chaine # dont les éléments sont séparés par des marqueurs ++X, e.g: # ++ ou ++c[md] une chaine simple résultat de la commande # ++s[split] un ensemble d'arguments résultat du split du résultat de la # commande sur espaces # ++l[split] un ensemble d'arguments résultat du split du résultat de la # commande sur les lignes # ++a[dd] un ensemble d'arguments donnés directement # Exemple: # array_buildcmd args echo Copie de ++ ppath "$src" ++a vers ++ ppath "$dest" local desta="$1"; shift; local "$desta" local mode=add local -a pending cmd while [ $# -gt 0 ]; do case "$1" in ++c|++cmd|++) __ab_process_pending; mode=cmd;; ++s|++ssplit) __ab_process_pending; mode=ssplit;; ++l|++lsplit) __ab_process_pending; mode=lsplit;; ++a|++add) __ab_process_pending; mode=add;; *) pending=("${pending[@]}" "$1");; esac shift done __ab_process_pending array_upvar "$desta" "${cmd[@]}" } function buildcmd() { local -a args array_buildcmd args "$@" qvals "${args[@]}" } function evalcmd() { local -a args array_buildcmd args "$@" "${args[@]}" }