##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ## Fonctions de base ##@cooked nocomments ##@*inc[base.ulib ## Support des fonctions uprovided(), uprovide() et urequire() de ulib dans le ## cas où cette librairie n'est pas chargée if [ -z "$ULIBDIR" -o "$ULIBDIR" != "$ULIBINIT" ]; then ULIBPROVIDED=() function uprovided() { local module for module in "${ULIBPROVIDED[@]}"; do [ "$module" == "$1" ] && return 0 done return 1 } function uprovide() { uprovided "$1" && return 1 ULIBPROVIDED=("${ULIBPROVIDED[@]}" "$1") } function urequire() { local module r=0 for module in "$@"; do uprovided "$module" && continue echo "error: $module: this module is required but cannot be automatically loaded" 1>&2 r=1 done return $r } uprovide base.ulib fi ##@*inc]base.ulib ##@include base.init ##@include base.core ##@include base.string ##@include base.num ##@include base.bool ##@include base.array ##@include base.quote ##@include base.split ##@include base.args ##@include base.tools ##@include base.compat ##@include base.deprecated uprovide base urequire base.init base.core base.string base.num base.bool base.array base.quote base.split base.args base.tools base.compat urequire base.deprecated ## variables tableaux function array_count() { # retourner le nombre d'éléments du tableau $1 eval "echo \${#$1[*]}" } function array_isempty() { # tester si le tableau $1 est vide [ $(array_count "$1") -eq 0 ] } function array_new() { # créer un tableau vide dont le nom est $1 eval "$1=()" } function array_add() { # ajouter les valeurs $2..@ au tableau dont le nom est $1 local __aa_a="$1"; shift eval "$__aa_a=(\"\${$__aa_a[@]}\" \"\$@\")" #eval "$1=(\"\${$1[@]}\" $(qval "$2"))" } function array_ins() { # insérer les valeurs $2..@ au début du tableau dont le nom est $1 local __aa_a="$1"; shift eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")" #eval "$1=($(qval "$2") \"\${$1[@]}\")" } function array_del() { # supprimer *les* valeurs $2 du tableau dont le nom est $1 local __ad_v local -a __ad_vs eval 'for __ad_v in "${'"$1"'[@]}"; do if [ "$__ad_v" != '"$(qval "$2")"' ]; then array_add __ad_vs "$__ad_v" fi done' array_copy "$1" __ad_vs } function array_addu() { # ajouter la valeur $2 au tableau dont le nom est $1, si la valeur n'y est pas # déjà. Retourner vrai si la valeur a été ajoutée local __as_v eval 'for __as_v in "${'"$1"'[@]}"; do if [ "$__as_v" == '"$(qval "$2")"' ]; then return 1 fi done' array_add "$1" "$2" return 0 } function array_set() { array_addu "$@" } function array_insu() { # insérer la valeur $2 au début du tableau tableau dont le nom est $1, si la # valeur n'y est pas déjà. Retourner vrai si la valeur a été ajoutée. local __as_v eval 'for __as_v in "${'"$1"'[@]}"; do if [ "$__as_v" == '"$(qval "$2")"' ]; then return 1 fi done' array_ins "$1" "$2" return 0 } function array_fillrange() { # Initialiser le tableau $1 avec les nombres de $2(=1) à $3(=10) avec un step de $4(=1) local -a __af_vs local __af_i="${2:-1}" __af_to="${3:-10}" __af_step="${4:-1}" while [ "$__af_i" -le "$__af_to" ]; do __af_vs=("${__af_vs[@]}" "$__af_i") __af_i=$(($__af_i + $__af_step)) done array_copy "$1" __af_vs } function array_eq() { # tester l'égalité des tableaux $1 et $2 local -a __ae_a1 __ae_a2 array_copy __ae_a1 "$1" array_copy __ae_a2 "$2" [ ${#__ae_a1[*]} -eq ${#__ae_a2[*]} ] || return 1 local __ae_v __ae_i=0 for __ae_v in "${__ae_a1[@]}"; do [ "$__ae_v" == "${__ae_a2[$__ae_i]}" ] || return 1 __ae_i=$(($__ae_i + 1)) done return 0 } function array_contains() { # tester si le tableau dont le nom est $1 contient la valeur $2 local __ac_v eval 'for __ac_v in "${'"$1"'[@]}"; do if [ "$__ac_v" == '"$(qvalm "$2")"' ]; then return 0 fi done' return 1 } function array_icontains() { # tester si le tableau dont le nom est $1 contient la valeur $2, sans tenir # compte de la casse local __ac_v eval 'for __ac_v in "${'"$1"'[@]}"; do if [ "$(strlower "$__ac_v")" == '"$(strlower "$(qval "$2")")"' ]; then return 0 fi done' return 1 } function array_find() { # si le tableau $1 contient la valeur $2, retourner l'index de la valeur. Si le # tableau $3 est spécifié, retourner la valeur à l'index dans ce tableau local __af_i __af_v __af_i=0 eval 'for __af_v in "${'"$1"'[@]}"; do if [ "$__af_v" == '"$(qval "$2")"' ]; then if [ -n "$3" ]; then echo "${'"$3"'[$__af_i]}" else echo "$__af_i" fi return 0 fi __af_i=$(($__af_i + 1)) done' return 1 } function array_reverse() { # Inverser l'ordre des élément du tableau $1 local -a __ar_vs local __ar_v array_copy __ar_vs "$1" array_new "$1" for __ar_v in "${__ar_vs[@]}"; do array_ins "$1" "$__ar_v" done } function array_replace() { # dans le tableau $1, remplacer toutes les occurences de $2 par $3..* local __ar_sn="$1"; shift local __ar_f="$1"; shift local -a __ar_s __ar_d local __ar_v array_copy __ar_s "$__ar_sn" for __ar_v in "${__ar_s[@]}"; do if [ "$__ar_v" == "$__ar_f" ]; then __ar_d=("${__ar_d[@]}" "$@") else __ar_d=("${__ar_d[@]}" "$__ar_v") fi done array_copy "$__ar_sn" __ar_d } function array_each() { # Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les # arguments '$v $3..$n' local __ae_an="$1"; shift local __ae_f="$1"; shift local -a __ae_a local __ae_v array_copy __ae_a "$__ae_an" for __ae_v in "${__ae_a[@]}"; do "$__ae_f" "$__ae_v" "$@" done } function array_map() { # Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les # arguments '$v $3..$n', et remplacer la valeur par le résultat de la fonction local __am_an="$1"; shift local __am_f="$1"; shift local -a __am_a __am_vs local __am_v array_copy __am_a "$__am_an" for __am_v in "${__am_a[@]}"; do __am_vs=("${__am_vs[@]}" "$("$__am_f" "$__am_v" "$@")") done array_copy "$__am_an" __am_vs } function first_value() { # retourner la première valeur du tableau $1 eval "recho \"\${$1[@]:0:1}\"" } function last_value() { # retourner la dernière valeur du tableau $1 eval "recho \"\${$1[@]:\$((-1)):1}\"" } function array_copy() { # copier le contenu du tableau $2 dans le tableau $1 eval "$1=(\"\${$2[@]}\")" } function array_copy_firsts() { # copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la dernière eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")" } function array_del_last() { array_copy_firsts "$1" } function array_copy_lasts() { # copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la première eval "$1=(\"\${${2:-$1}[@]:1}\")" } function array_del_first() { array_copy_lasts "$1" } function array_extend() { # ajouter le contenu du tableau $2 au tableau $1 eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")" } function array_extendu() { # ajouter chacune des valeurs du tableau $2 au tableau $1, si ces valeurs n'y # sont pas déjà. Retourner vrai si au moins une valeur a été ajoutée local __ae_v __ae_s=1 eval 'for __ae_v in "${'"$2"'[@]}"; do array_addu "$1" "$__ae_v" && __ae_s=0 done' return "$__ae_s" } function array_extend_firsts() { # ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la dernière eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")" } function array_extend_lasts() { # ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la première eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")" } function array_xsplit() { # créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés # par $3, qui vaut ':' par défaut). eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" }'))" #" } function array_split() { # créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés # par $3, qui vaut ':' par défaut). Les éléments vides sont ignorés. par exemple # "a::b" est équivalent à "a:b" eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' /^$/ { next } { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" }'))" #" } function array_from_path() { array_split "$1" "$2" ":" } function array_from_xlines() { # créer le tableau $1 avec chaque ligne de $2. eval "$1=($(recho_ "$2" | _nl2lf | awk ' { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" }'))" #" } function array_from_lines() { # créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont ignorés. eval "$1=($(recho_ "$2" | _nl2lf | awk ' /^$/ { next } { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" }'))" #" } function array_join() { # afficher le contenu du tableau dont le nom est $1 sous forme d'une liste de # valeurs séparées par $2 (par défaut, une virgule) # Si $1=="@", alors les éléments du tableaux sont les arguments de la fonction à # partir de $3 # Si $1!="@" et que le tableau est vide, afficher $3 # Si $1!="@", $4 et $5 sont des préfixes et suffixes à rajouter à chaque élément local __aj_an __aj_l __aj_j __aj_s="${2:-,}" __aj_pf __aj_sf if [ "$1" == "@" ]; then __aj_an="\$@" shift; shift else __aj_an="\${$1[@]}" __aj_pf="$4" __aj_sf="$5" fi eval 'for __aj_l in "'"$__aj_an"'"; do __aj_j="${__aj_j:+$__aj_j'"$__aj_s"'}$__aj_pf$__aj_l$__aj_sf" done' if [ -n "$__aj_j" ]; then recho "$__aj_j" elif [ "$__aj_an" != "\$@" -a -n "$3" ]; then recho "$3" fi } function array_mapjoin() { # map le tableau $1 avec la fonction $2, puis afficher le résultat en séparant # chaque élément par $3. Les arguments et la sémantique sont les mêmes que pour # array_join en tenant compte de l'argument supplémentaire $2 qui est la # fonction pour array_map (les autres arguments sont décalés en conséquence) local __amj_src="$1" __amj_func="$2" __amj_sep="$3" shift; shift; shift if [ "$__amj_src" == "@" ]; then local -a __amj_tmpsrc __amj_tmpsrc=("$@") __amj_src=__amj_tmpsrc set -- fi local -a __amj_tmp array_copy __amj_tmp "$__amj_src" array_map __amj_tmp "$__amj_func" array_join __amj_tmp "$__amj_sep" "$@" } function array_to_lines() { # afficher le tableau dont le nom est $1 sous forme de lignes array_join "$1" " " "$2" "$3" "$4" } function array_to_path() { # afficher le tableau dont le nom est $1 sous forme d'une liste de chemins # séparés par ':') array_join "$1" ":" "$2" "$3" "$4" } function array_fix_paths() { # Corriger les valeurs du tableau $1. Les valeurs contenant le séparateur # $2(=':') sont séparées en plusieurs valeurs. Par exemple avec le tableau # input=(a b:c), le résultat est input=(a b c) local __afp_an="$1" __afp_s="${2:-:}" local -a __afp_vs local __afp_v array_copy __afp_vs "$__afp_an" array_new "$__afp_an" for __afp_v in "${__afp_vs[@]}"; do array_split __afp_v "$__afp_v" "$__afp_s" array_extend "$__afp_an" __afp_v done } ################################################################################ ## dates function get_date_rfc822() { LC_TIME=C date +"%a, %d %b %Y %H:%M:%S %Z" } function get_date_fr() { LC_TIME=C date +"%d/%m/%Y" } function get_time_fr() { LC_TIME=C date +"%Hh%M" } function parse_date() { local value="$1" type="${2:-date}" local now="$(awk 'BEGIN { print mktime(strftime("%Y %m %d 00 00 00 +0400")) }')" case "$value" in +*) value="$(($now + ${value#+} * 86400))";; *) value="$(<<<"$value" awk -F/ '{ nd = strftime("%d"); nm = strftime("%m"); ny = strftime("%Y") d = $1 + 0; if (d < 1) d = nd; m = $2 + 0; if (m < 1) m = nm; if ($3 == "") y = ny; else { y = $3 + 0; if (y < 100) y = y + 2000; } print mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)); }')";; esac case "$type" in d|date) awk '{ print strftime("%d/%m/%Y", $0 + 0) }' <<<"$value";; l|ldap) awk '{ print strftime("%Y%m%d%H%M%S+0400", $0 + 0) }' <<<"$value";; m|mysql) awk '{ print strftime("%Y-%m-%d", $0 + 0) }' <<<"$value";; *) recho "$value";; esac } ################################################################################ ## chemins function udelpath() { # supprimer le chemin $1 de $2(=PATH) local _qdir="${1//\//\\/}" eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'=' } function uaddpath() { # Ajouter le chemin $1 à la fin, dans $2(=PATH), s'il n'y existe pas déjà local _qdir="${1//\//\\/}" eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"' } function uinspathm() { # Ajouter le chemin $1 au début, dans $2(=PATH), s'il n'y existe pas déjà local _qdir="${1//\//\\/}" eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"' } function uinspath() { # S'assurer que le chemin $1 soit au début de $2(=PATH) udelpath "$@" uinspathm "$@" } function withpath() { # tester si le chemin est relatif à . ou à .., ou est absolu. i.e 'withpath a/b' # renvoie faux alors que 'withpath ./a/b' renvoie vrai [ "${1#./}" != "$1" -o "${1#../}" != "$1" -o "${1#/}" != "$1" ] } function withext() { # tester si le fichier a une extension local basename="$(basename -- "$1")" [ "${basename%.*}" != "$basename" ] } function normpath() { # normaliser le chemin $1, qui est soit absolu, soit relatif à $2 (qui vaut # $(pwd) par défaut) local -a parts local part ap array_split parts "$1" / if [ "${1#/}" != "$1" ]; then ap=/ elif [ -n "$2" ]; then ap="$2" else ap="$(pwd)" fi for part in "${parts[@]}"; do if [ "$part" == "." ]; then continue elif [ "$part" == ".." ]; then ap="${ap%/*}" [ -n "$ap" ] || ap=/ else [ "$ap" != "/" ] && ap="$ap/" ap="$ap$part" fi done recho "$ap" } function abspath() { # Retourner un chemin absolu vers $1. Si $2 est non nul et si $1 est un chemin # relatif, alors $1 est exprimé par rapport à $2, sinon il est exprimé par # rapport au répertoire courant. # Si le chemin n'existe pas, il n'est PAS normalisé. Sinon, les meilleurs # efforts sont faits pour normaliser le chemin. local ap="$1" if [ "${ap#/}" != "$ap" ]; then # chemin absolu. on peut ignorer $2 __normpath "$ap" && return else # chemin relatif. il faut exprimer le chemin par rapport à $2 local cwd if [ -n "$2" ]; then cwd="$(abspath "$2")" else cwd="$(pwd)" fi ap="$cwd/$ap" __normpath "$ap" && return fi # dans les cas spéciaux, il faut calculer "manuellement" le répertoire absolu normpath "$ap" } function __normpath() { # normaliser dans les cas simple le chemin absolu $1. sinon retourner 1. # cette fonction est utilisée par abspath() if [ -d "$1" ]; then if [ -x "$1" ]; then # le cas le plus simple: un répertoire dans lequel on peut entrer (cd "$1"; pwd) return 0 fi elif [ -f "$1" ]; then local dn="$(dirname -- "$1")" bn="$(basename -- "$1")" if [ -x "$dn" ]; then # autre cas simple: un fichier situé dans un répertoire dans lequel # on peut entrer (cd "$dn"; echo "$(pwd)/$bn") return 0 fi fi return 1 } function parentdirs() { # Obtenir la liste de tous les parents du répertoire $2 dans le tableau $1, du # répertoire $2 vers la racine. Si $3 commence par 'r' (comme reverse), l'ordre # est inversé: le tableau contient les répertoire de la racine vers $2. array_new "$1" local __pd_d="$(abspath "$2")" if [[ "$3" == r* ]]; then while [ "$__pd_d" != "/" ]; do array_ins "$1" "$__pd_d" __pd_d="$(dirname "$__pd_d")" done else while [ "$__pd_d" != "/" ]; do array_add "$1" "$__pd_d" __pd_d="$(dirname "$__pd_d")" done fi } function ppath() { # Dans un chemin *absolu*, remplacer "$HOME" par "~" et "$(pwd)/" par "", afin # que le chemin soit plus facile à lire. Le répertoire courant est spécifié par # $2 ou $(pwd) si $2 est vide local path="$1" cwd="$2" path="$(abspath "$path")" # essayer de normaliser le chemin [ -n "$cwd" ] || cwd="$(pwd)" [ "$path" = "$cwd" ] && path="." [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path#$cwd/}" [ "${path#$HOME/}" != "$path" ] && path="~${path#$HOME}" recho "$path" } function ppath2() { # Comme ppath() mais afficher '.' comme '../$dirname' local path="$1" cwd="$2" path="$(abspath "$path")" # essayer de normaliser le chemin [ -n "$cwd" ] || cwd="$(pwd)" [ "$path" = "$cwd" ] && path="../$(basename -- "$path")" [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path#$cwd/}" [ "${path#$HOME/}" != "$path" ] && path="~${path#$HOME}" recho "$path" } function relpath() { # Afficher le chemin relatif de $1 par rapport à $2. Si $2 n'est pas spécifié, # on prend le répertoire courant. Si $1 ou $2 ne sont pas des chemins absolus, # il sont transformés en chemins absolus par rapport à $3. Si $1==$2, retourner # une chaine vide local p="$(abspath "$1" "$3")" cwd="$2" if [ -z "$cwd" ]; then cwd="$(pwd)" else cwd="$(abspath "$cwd" "$3")" fi if [ "$p" == "$cwd" ]; then echo "" elif [ "${p#$cwd/}" != "$p" ]; then recho "${p#$cwd/}" else local rp while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do rp="${rp:+$rp/}.." cwd="${cwd%/*}" done rp="$rp/${p#$cwd/}" # ${rp%//} traite le cas $1==/ echo "${rp%//}" fi } function relpathx() { # Comme relpath, mais pour un chemin vers un exécutable qu'il faut lancer: # s'assurer qu'il y a une spécification de chemin, e.g. ./script local p="$(relpath "$@")" if [ -z "$p" ]; then echo . elif [ "${p#../}" != "$p" -o "${p#./}" != "$p" ]; then echo "$p" else echo "./$p" fi } function withinpath() { # Tester si le chemin absolu $2 se trouve dans le chemin absolu "$1" (appelée # barrière). Soit un chemin P, on considère que P est dans P. Si ce comportement # n'est pas souhaité, $3(=N) doit valoir O, auquel cas P est dans Q implique que # P != Q. local b="$1" p="$2" strict="${3:-N}" b="$(abspath "$b")" p="$(abspath "$p")" if is_yes "$strict"; then [ "${p#$b/}" != "$p" ] else [ "$p" == "$b" -o "${p#$b/}" != "$p" ] fi } function safe_abspath() { # Afficher le chemin absolu de $1, par rapport à $2, si et seulement si le # chemin résultat ne se trouve pas en dehors de la barrière $3. Si $2 n'est pas # spécifié, prendre le répertoire courant. S'il est relatif, l'exprimer par # rapport au répertoire courant. Si $3 est relatif, l'exprimer par rapport à $2. # Si le chemin résultat est sité en dehors de la barrière, ne rien afficher et # retourner un code d'erreur. # Si le chemin $1 n'existe pas, il n'est PAS normalisé. Sinon, les meilleurs # efforts sont faits pour normaliser le chemin résultat. local p="$1" ba="$2" br="$3" # calculer basedir if [ -n "$ba" ]; then ba="$(abspath "$ba")" else ba="$(pwd)" fi # calculer barrier. l'exprimer relativement à basedir le cas échéant [ -n "$br" ] || br="$ba" br="$(abspath "$br" "$ba")" # puis calculer le chemin absolu p="$(abspath "$p" "$ba")" # vérifier la barrière if [ "$p" == "$br" -o "${p#$br/}" != "$p" ]; then echo "$p" else return 1 fi } function safe_relpath() { # Afficher le chemin relatif de $1 par rapport à $2 si et seulement si le chemin # résultat ne se trouve pas en dehors de la barrière $3. Si $2 n'est pas # spécifié, prendre le répertoire courant. S'il est relatif, l'exprimer par # rapport au répertoire courant. Si $3 est relatif, l'exprimer par rapport à $2. # Si le chemin résultat est sité en dehors de la barrière, ne rien afficher et # retourner un code d'erreur. local p if p="$(safe_abspath "$1" "$2" "$3")"; then relpath "$p" "$2" "$(pwd)" else return 1 fi } function splitwcs() { # Découper un nom de chemin $1 entre la partie sans wildcards, qui est placée dans # la variables $2(=basedir), et la partie avec wildcards, qui est placée dans la # variable $3(=filespec) local __sw_p="$1" local __sw_dd="${2:-basedir}" __sw_df="${3:-filespec}" __sw_part __sw_d __sw_f local -a __sw_parts array_split __sw_parts "$__sw_p" "/" for __sw_part in "${__sw_parts[@]}"; do if [[ "$__sw_part" == *\** ]] || [[ "$__sw_part" == *\?* ]] || [ -n "$__sw_f" ]; then __sw_f="${__sw_f:+$__sw_f/}$__sw_part" else __sw_d="${__sw_d:+$__sw_d/}$__sw_part" fi done [ "${__sw_p#/}" != "$__sw_p" ] && __sw_d="/$__sw_d" _setv "$__sw_dd" "$__sw_d" _setv "$__sw_df" "$__sw_f" } function deref() { # Retourner un chemin absolu vers le fichier $1, dans lequel toutes les # composantes "lien symbolique" ont été supprimées. # DEPRECATED: Cette fonction est dépréciée. Utiliser à la place readlinkm() local OENC="$UTF8" local max_deref=50 local file="$1" local basedir link while [ -L "$file" ]; do basedir="$(dirname "$file")" link="$(readlink "$file")" if first_char_is "$link" "/"; then # lien absolu file="$link" else # lien relatif file="$basedir/$link" fi max_deref=$(($max_deref - 1)) [ $max_deref -eq 0 ] && die "Plus de 50 indirection. Le lien $file est-il récursif?" done abspath "$file" } function readlinka() { # Afficher un chemin absolu vers la destination du fichier $1. Si $1 n'est pas # un lien, afficher simplement le chemin du fichier if [ -L "$1" ]; then local linkdir="$(dirname -- "$1")" abspath "$(readlink "$1")" "$linkdir" else abspath "$1" fi } function readlinkm() { # Retourner un chemin absolu vers le fichier $1, dans lequel toutes les # composantes "lien symbolique" ont été supprimées. Il n'est pas requis que les # composantes du chemin existent. readlink -m "$1" } function path_if_test() { # afficher un chemin si le fichier $2 existe (en utilisant l'opérateur $1) dans # l'un des chemins absolus $4..n. si $3==relative, afficher le chemin relatif, # sinon le chemin absolu. note: $3 peut être de la forme relative:path, auquel # cas le chemin affiché est exprimé relativement à path local op="$1"; shift local file="$1"; shift local rel="$1" reldir=; shift if beginswith "$rel" relative; then reldir="${rel#relative}" if beginswith "$reldir" :; then # on a un argument de la forme relative:path reldir="${reldir#:}" if [ -n "$reldir" ]; then reldir="${reldir}/" fi else # argument vide ou format non valide reldir= fi else rel= fi while [ -n "$1" ]; do local basedir="$1" if [ $op "$basedir/$file" ]; then if [ -n "$rel" ]; then recho "$reldir$file" else recho "$basedir/$file" fi break fi shift done } function update_link() { # mettre à jour le lien $2 pour qu'il pointe vers le fichier $1 [ -L "$2" ] || return 1 local dest link="$2" local linkdir="$(dirname "$link")" local ldest="$(readlink "$link")" if [ "${ldest#/}" != "$ldest" ]; then # c'est un lien absolu, faire un lien absolu dest="$(abspath "$1")" else # c'est un lien relatif, faire un lien relatif dest="$(relpath "$1" "$linkdir")" fi if [ "$dest" == "$ldest" ]; then : # pas besoin de mettre à jour elif [ -d "$link" ]; then rm -f "$link" && ln -s "$dest" "$link" else ln -sf "$dest" "$link" fi } function update_links() { # Mettre à jour les liens $2..@ pour qu'ils pointent vers la nouvelle # destination $1 [ -n "$1" ] || return 1 local dest="$1"; shift local r=0 link for link in "$@"; do update_link "$dest" "$link" || r=$? done return $r } function move_link() { # Déplacer le lien $1 vers $2, et mettre à jour la destination du lien si # elle est exprimée de façon relative # Si $1 n'est pas un lien, le déplacer normalement avec mv [ -n "$1" -a -n "$2" ] || return 1 local link="$1" dest="$2" [ -d "$dest" ] && dest="$dest/$(basename -- "$link")" dest="$(abspath "$dest")" if [ -L "$link" ]; then link="$(abspath "$link")" [ "$dest" == "$link" ] && return 0 ldest="$(readlinka "$link")" mv "$link" "$dest" || return 1 update_link "$ldest" "$dest" else [ "$dest" == "$link" ] && return 0 mv "$link" "$dest" fi } function copy_link() { # Copier le lien $1 vers $2, et mettre à jour la destination du lien si # elle est exprimée de façon relative # Si $1 n'est pas un lien, le copier normalement avec cp [ -n "$1" -a -n "$2" ] || return 1 local link="$1" dest="$2" [ -d "$dest" ] && dest="$dest/$(basename -- "$link")" dest="$(abspath "$dest")" if [ -L "$link" ]; then link="$(abspath "$link")" [ "$dest" == "$link" ] && return 0 ldest="$(readlinka "$link")" cp -P "$link" "$dest" || return 1 update_link "$ldest" "$dest" else [ "$dest" == "$link" ] && return 0 cp "$link" "$dest" fi } function array_find_links() { # Chercher dans le répertoire $3 (qui est par défaut le répertoire courant) # les liens vers le fichier $2, et ajouter leurs chemins absolus dans le # tableau $1 local -a __afl_links __afl_result local __afl_dir="${3:-.}" local __afl_dest __afl_destname __afl_link __afl_linkdir __afl_ldest __afl_dest="$(abspath "$2")" __afl_destname="${__afl_dest##*/}" array_from_lines __afl_links "$(find "$__afl_dir" -type l)" for __afl_link in "${__afl_links[@]}"; do __afl_ldest="$(readlink "$__afl_link")" # optimiser le calcul: pas besoin d'aller plus loin si les noms ne # correspondent pas if [ "$__afl_ldest" != "$__afl_destname" ]; then [[ "$__afl_ldest" == */"$__afl_destname" ]] || continue fi # nous avons un candidate, tester si les chemins correspondent __afl_link="$(abspath "$__afl_link" "$__afl_dir")" __afl_linkdir="$(dirname -- "$__afl_link")" __afl_ldest="$(abspath "$__afl_ldest" "$__afl_linkdir")" if [ "$__afl_ldest" == "$__afl_dest" ]; then array_add __afl_result "$__afl_link" fi done array_copy "$1" __afl_result } function list_links() { # Chercher dans le répertoire $2 les liens vers le fichier $1, et les # afficher, un par ligne. local -a links array_find_links links "$@" array_to_lines links } function move_file() { # Déplacer le fichier $1 vers $2, et mettre à jour les liens $3..@ pour # qu'ils pointent vers la nouvelle destination [ -n "$1" -a -n "$2" ] || return 1 local src="$1" dest="$2" link shift; shift [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" move_link "$src" "$dest" || return 1 update_links "$dest" "$@" } ################################################################################ ## utilitaires function get_nblines() { # Afficher le nombre de lignes d'un fichier [ -f "$1" ] && sed -ne '$=' "$1" || echo 0 } function mktempf() { # générer un fichier temporaire et retourner son nom mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}" } function mktempd() { # générer un répertoire temporaire et retourner son nom mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}" } function mkdirof() { # Créer le répertoire correspondant à un fichier mkdir -p "$(dirname -- "$1")" } function cp_a() { # copier des fichiers en gardant le maximum de propriétés /bin/cp -a "$@" } function cp_R() { # copier des fichiers récursivement, en suivant les liens symboliques /bin/cp -pR "$@" } function quietgrep() { # tester la présence d'un pattern dans un fichier grep -q "$@" 2>/dev/null } function quietdiff() { # tester si deux fichiers sont identiques diff -q "$@" >&/dev/null } function testsame() { # tester si deux fichiers sont identiques/différents quietdiff "$@" } function testdiff() { ! quietdiff "$@" } function testupdated() { # tester si $2 n'existe pas ou si $1 est différent de $2. Si $3 est non vide, # la valeur de retour est fixée à 0 (forcer à considérer le test comme vrai) if [ -n "$3" ]; then return 0 elif [ -f "$2" ]; then testdiff "$1" "$2" else return 0 fi } function testnewer() { # test si $2 n'existe pas ou si $1 est plus récent que $2. Si $3 est non vide, # la valeur de retour est fixée à 0 (forcer à considérer le test comme vrai) [ -n "$3" ] && return 0 test ! -e "$2" -o "$1" -nt "$2" } function ps_all() { # afficher tous les processus avec le maximum d'informations ps -axww } function progexists() { # tester l'existence d'un programme dans le PATH test -n "$1" -a -x "$(which "$1" 2>/dev/null)" } function has_python() { # tester la présence de python progexists python } function has_gawk() { # tester la présence de gnuawk progexists gawk } function is_root() { # tester si on est root test `id -u` -eq 0 } function source_ifexists() { # sourcer un fichier s'il existe if [ -f "$1" ]; then source "$1" || die; fi } function little_sleep { # s'endormir pour une très petite période de temps LC_NUMERIC=C sleep 0.1 } function random_sleep { # s'endormir pour une durée allant de 0 à $1 secondes. Par défaut, $1=1800 (soit 30 minutes) sleep $(($RANDOM % ${1:-1800})) } function is_running() { # tester si un programme dont on donne le PID tourne kill -0 "$1" >&/dev/null } function sedi() { # Lancer sed sur un fichier en le modifiant en place sed -i "$@" } function csort() { # Lancer sort avec LANG=C pour éviter les problèmes avec la locale. en effet, # avec LANG!=C, sort utilise les règles de la locale pour le tri, et par # exemple, avec LANG=fr_FR.UTF-8, la locale indique que les ponctuations doivent # être ignorées. LANG=C sort "$@" } function lsort() { sort "$@"; } function cgrep() { # Lancer grep avec LANG=C pour éviter les problèmes avec la locale. cf csort # pour une explication. LANG=C grep "$@" } function lgrep() { grep "$@"; } function csed() { # Lancer sed avec LANG=C pour éviter les problèmes avec la locale. cf csort pour # une explication. LANG=C sed "$@" } function lsed() { sed "$@"; } function cawk() { # Lancer awk avec LANG=C pour éviter les problèmes avec la locale. cf csort pour # une explication. LANG=C awk "$@" } function lawk() { awk "$@"; } function cdiff() { # Lancer diff avec LANG=C pour éviter les problèmes avec la locale. cf csort # pour une explication. LANG=C diff "$@" } function ldiff() { diff "$@"; } ################################################################################ ## utilitaires de haut niveau function fix_mode() { # Si le fichier $1 n'est pas writable, le rendre writable temporairement. Si # nécessaire, le fichier est créé. # Cette fonction s'utilise de cette façon: # mode="$(fix_mode file)" # ... # unfix_mode file "$mode" local file="$1" [ -f "$file" ] || touch "$file" || return 1 if [ ! -w "$file" ]; then local mode="$(stat -c %a "$file")" chmod ${mode:0:${#mode}-3}6${mode:${#mode}-2:2} "$file" echo "$mode" fi } function unfix_mode() { # Restaurer le mode $2 du fichier $1 traité par fix_mode [ -n "$2" ] && chmod "$2" "$1" } function get_mode() { # Obtenir le mode du fichier $1, en le créant si nécessaire. A utiliser avec # unfix_mode pour restaurer le mode d'un fichier qui a été traité avec un # fichier temporaire intermédiaire [ -f "$1" ] || touch "$1" || return 1 stat -c %a "$1" } function rm_maybe() { # Supprimer les fichiers dont on donne la liste. Si aucun fichier n'est # spécifié, cette fonction est un NOP local parse_opts=1 arg rm for arg in "$@"; do # chercher s'il y a un argument autre que des options if [ -n "$parse_opts" ]; then if [ "$arg" == "--" ]; then parse_opts= elif [[ "$arg" == "-*" ]]; then continue elif [ -n "$arg" ]; then rm=1 break fi elif [ -n "$arg" ]; then rm=1 break fi done [ -n "$rm" ] && /bin/rm "$@" } __CPDIR_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) __CPDIR_RSYNC_ARGS=(-q) function cpdir() { # copier un fichier dans un répertoire, ou le contenu d'un répertoire dans un # autre répertoire, que le répertoire source soit un lien symbolique ou # non. Cette fonction existe parce que le comportement de "cp_a src dest" n'est # pas consistant selon les plateformes, surtout si src est un lien symbolique # sur un répertoire: parfois on copie le lien, parfois on copie le contenu du # répertoire, parfois on copie le répertoire... # La copie est faite avec rsync si possible. Les options du tableau # __CPDIR_RSYNC_ARGS sont rajoutées aux options standard de rsync. if progexists rsync; then [ -d "$2" ] || mkdir -p "$2" || return 1 if [ -d "$1" ]; then rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1/" "$2/" else rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1" "$2/" fi else __cpdir "$@" fi } function __cpdir() { # implémentation avec bash de cpdir(). cette fonction est utilisée par # cpdir() quand rsync n'est pas disponible # $1=src, $2=dest, $3=method (cp_a par défaut) local src="$1" dest="$2" method="${3:-cp_a}" if [ -d "$src" ]; then # si c'est un répertoire, traitement particulier # tout d'abord, s'assurer que la destination existe [ -d "$dest" ] || mkdir -p "$dest" || return 1 # ensuite on fait la copie local prevdir="$(pwd)" dest="$(abspath "$dest")" cd "$src" if [ -n "$(/bin/ls -a1)" ]; then # copier les fichiers [ -n "$(/bin/ls -1)" ] && "$method" * "$dest" # ne pas oublier les fichiers cachés... local i for i in .*; do [ "$i" == "." -o "$i" == ".." ] && continue "$method" "$i" "$dest" done fi cd "$prevdir" else # sinon, on assume que c'est un fichier if [ -f "$dest" ]; then # copie du fichier avec remplacement "$method" "$src" "$dest" elif [ -d "$dest" ]; then # copie du fichier dans le répertoire "$method" "$src" "$dest" else # Copie du fichier dans le répertoire $dest qui est créé pour la # circonstance mkdir -p "$dest" "$method" "$src" "$dest" fi fi } __CPNOVCS_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) __CPNOVCS_RSYNC_ARGS=(-q) __CPNOVCS_INCLUDE_VCS= # ne pas ignorer les répertoires de VCS function cpnovcs() { # copier le fichier/répertoire $1 *dans* le *répertoire* $2 avec rsync. Les # options du tableau __CPNOVCS_RSYNC_ARGS sont rajoutées aux options standard # de rsync. # Si $1 est un répertoire, la copie est faite en ignorant les sous-répertoires # de VCS (.svn, CVS). En ce qui concerne les répertoire de VCS, git aussi est # supporté, mais uniquement s'il est à la racine du transfert. # Si $1 se termine par un '/', c'est le contenu du répertoire qui est copié, pas # le répertoire lui-même. Si rsync n'est pas trouvé sur le système, alors on # fait une copie standard qui inclue les répertoires de VCS. local src="$1" destdir="$2" [ -d "$destdir" ] || mkdir -p "$destdir" || return 1 if progexists rsync; then local -a novcs if [ -z "$__CPNOVCS_INCLUDE_VCS" ]; then local gitexclude=/.git/ [ "${src%/}" == "$src" ] && gitexclude="/$(basename -- "$src")$gitexclude" novcs=(--exclude CVS/ --exclude .svn/ --exclude "$gitexclude") fi rsync -a ${__CPNOVCS_RSYNC_SLOW:+-c} "${novcs[@]}" "${__CPNOVCS_RSYNC_ARGS[@]}" "$src" "$destdir/" elif [ "${src%/}" != "$src" ]; then __cpdir "$src" "$destdir" else local srcname="$(basename -- "$src")" mkdir -p "$destdir/$srcname" __cpdir "$src" "$destdir/$srcname" fi } function cpvcs() { # comme cpnovcs, mais ne pas ignorer les répertoires de VCS local __CPNOVCS_INCLUDE_VCS=1 cpnovcs "$@" } function cpdirnovcs() { # Le pendant de cpdir, mais en ignorant les sous-répertoires de VCS: copier le # contenu du répertoire $1 dans le répertoire $2 if [ -d "$1" ]; then cpnovcs "$1/" "$2" else cpnovcs "$1" "$2" fi } function doinplace() { # Filtrer le fichier $1 à travers la commande $2..$*, puis remplacer le fichier # s'il n'y a pas eu d'erreur. Retourner le code d'erreur de la commande. Si $1 # n'est pas spécifié ou vaut -, filtrer l'entrée standard vers la sortie # standard. # La variante doinplacef remplace le fichier quelque soit le code de retour de # la commande. A utiliser avec des commandes comme grep qui peuvent retourner # FAUX s'ils ne trouvent pas le motif if [ -n "$1" -a "$1" != "-" ]; then local __dip_file="$1"; shift autoclean "$__dip_file.tmp.$$" "$@" <"$__dip_file" >"$__dip_file.tmp.$$" local s=$? [ "$s" == 0 ] && /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" /bin/rm -f "$__dip_file.tmp.$$" return $s else shift "$@" fi } function doinplacef() { if [ -n "$1" -a "$1" != "-" ]; then local __dip_file="$1"; shift autoclean "$__dip_file.tmp.$$" "$@" <"$__dip_file" >"$__dip_file.tmp.$$" local s=$? /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" /bin/rm -f "$__dip_file.tmp.$$" return $s else shift "$@" fi } function stripnl() { # Supprimer les caractères de fin de ligne de la chaine en entrée tr -d '\r\n' } function _nl2lf() { # transformer les fins de ligne en LF awk 'BEGIN {RS="\r|\r\n|\n"} {print}' } function nl2lf() { doinplace "$1" _nl2lf } function _nl2crlf() { # transformer les fins de ligne en CRLF awk 'BEGIN {RS="\r|\r\n|\n"} {print $0 "\r"}' } function nl2crlf() { doinplace "$1" _nl2crlf } function _nl2cr() { # transformer les fins de ligne en CR awk 'BEGIN {RS="\r|\r\n|\n"; ORS=""} {print $0 "\r"}' } function nl2cr() { doinplace "$1" _nl2cr } function _latin1compat() { # transformer certains caractères UTF-8 en leur équivalent UTF-8 transformable # en latin1. LANG=fr_FR.UTF-8 sed $' s/[\xE2\x80\x90\xE2\x80\x91\xE2\x80\x92\xE2\x80\x93\xE2\x80\x94\xE2\x80\x95]/-/g s/[‘’]/\x27/g s/[«»“”]/"/g s/[\xC2\xA0\xE2\x80\x87\xE2\x80\xAF\xE2\x81\xA0]/ /g s/[œ]/oe/g s/[Œ]/OE/g s/[æ]/ae/g s/[Æ]/AE/g ' } function _noaccents() { # supprimer les accents d'un flux en UTF-8 LANG=fr_FR.UTF-8 sed ' s/[à]/a/g s/[éèêë]/e/g s/[ïî]/i/g s/[ôö]/o/g s/[üû]/u/g s/[ç]/c/g s/[À]/A/g s/[ÉÈÊË]/E/g s/[ÏÎ]/I/g s/[ÔÖ]/O/g s/[ÜÛ]/U/g s/[Ç]/C/g ' } function list_all() { # Lister les fichiers ou répertoires du répertoire $1, un par ligne # Les répertoires . et .. sont enlevés de la liste # $1=un répertoire dont le contenu doit être listé # $2..@=un ensemble de patterns pour le listage local curdir="$(pwd)" local b="${1:-.}"; shift cd "$b" 2>/dev/null || return eval "$(__la_cmd "$@")" | while read f; do [ "$f" == "." -o "$f" == ".." ] && continue recho "$f" done cd "$curdir" } function __la_cmd() { [ $# -gt 0 ] || set '*' local arg local cmd="/bin/ls -1d" for arg in "$@"; do arg="$(qwc "$arg")" cmd="$cmd $arg" done cmd="$cmd 2>/dev/null" echo "$cmd" } function list_files() { # Lister les fichiers du répertoire $1, un par ligne # $1=un répertoire dont le contenu doit être listé. # $2..@=un ensemble de patterns pour le listage local f local curdir="$(pwd)" local b="${1:-.}"; shift cd "$b" 2>/dev/null || return eval "$(__la_cmd "$@")" | while read f; do [ -f "$f" ] && recho "$f" done cd "$curdir" } function list_dirs() { # Lister les répertoires du répertoire $1, un par ligne # Les répertoires . et .. sont enlevés de la liste # $1=un répertoire dont le contenu doit être listé. # $2..@=un ensemble de patterns pour le listage local f local curdir="$(pwd)" local b="${1:-.}"; shift cd "$b" 2>/dev/null || return eval "$(__la_cmd "$@")" | while read f; do [ "$f" == "." -o "$f" == ".." ] && continue [ -d "$f" ] && recho "$f" done cd "$curdir" } function __array_ls() { # Lister les fichiers avec `list_$1 $3 $4...`, et les mettre dans le tableau # $2. Note: le tableau contient les chemins complets, par seulement les noms # comme avec list_$1 local __al_l="list_${1:-all}"; shift local __al_an="$1"; shift local __al_d="${1:-.}"; shift local -a __al_fs array_from_lines __al_fs "$("$__al_l" "$__al_d" "$@")" local __al_f array_new "$__al_an" for __al_f in "${__al_fs[@]}"; do array_add "$__al_an" "$__al_d/$__al_f" done } function array_lsall() { # Lister les fichiers avec `list_all $2 $3...`, et les mettre dans le # tableau $1. Le tableau contient les chemins complets, par seulement les # noms comme avec list_all __array_ls all "$@" } function array_lsdirs() { # Lister les fichiers avec `list_dirs $2 $3...`, et les mettre dans le # tableau $1. Le tableau contient les chemins complets, par seulement les # noms comme avec list_dirs __array_ls dirs "$@" } function array_lsfiles() { # Lister les fichiers avec `list_files $2 $3...`, et les mettre dans le # tableau $1. Le tableau contient les chemins complets, par seulement les # noms comme avec list_files __array_ls files "$@" } function filter_empty() { # Filtrer l'entrée standard en enlevant les lignes vides sed '/^$/d' } function filter_vcspath() { # L'entrée standard étant une liste de chemins, filtrer les fichiers et # répertoire qui ont un rapport avec subversion ou git sed ' /^.git$/d /^.git\//d /\/.git$/d /\/.git\//d /^.svn$/d /^.svn\//d /\/.svn$/d /\/.svn\//d ' } function merge_contlines() { # Avec les lignes lues sur stdin, fusionner celles qui se terminent par \ avec # les suivantes. awk 'substr($0, length($0)) == "\\" { while (getline nextline) { $0 = substr($0, 1, length($0) - 1) nextline if (substr($0, length($0)) != "\\") break } print next } {print}' } function filter_comment() { # Filtrer un fichier de configuration lu sur stdin en enlevant les commentaires # et les lignes vides. # Avec $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes # Comme filter_conf(), les commentaires doivent être sur une ligne à part. # Contrairement à filter_conf, il n'est pas nécessaire que le caractère '#' soit # en début de ligne: il peut apparaitre après des espaces et des tabulations. De # même, une ligne qui ne contient que des espaces et des tabulations est # considérée comme vide. local -a merge [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) awk ' /^[ \t]*#/ { next } /^[ \t]*$/ { next } { print }' | "${merge[@]}" } function filter_conf() { # filtrer un fichier de configuration lu sur stdin en enlevant les commentaires # et les lignes vides. Une ligne n'est considérée commentaire que si '#' est un # première position. Utiliser filter_comment() si les commentaire peuvent # commencer par des caractères espace et tabulation. # Si $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes local -a merge [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) grep -v '^#' | grep -v '^$' | "${merge[@]}" } function is_archive() { # tester si l'extension d'un fichier indique que c'est une archive local name="${1%.zip}" name="${name%.tgz}" name="${name%.tbz2}" name="${name%.tar.gz}" name="${name%.tar.bz2}" name="${name%.tar}" name="${name%.jar}" name="${name%.war}" name="${name%.ear}" [ "$name" != "$1" ] } function extract_archive() { # Extraire le contenu de l'archive $1 dans le répertoire ${2:-.} # Les autres arguments, s'ils sont spécifiés, indiquent les fichiers à extraire local arch="$1" destdir="${2:-.}" shift; shift if endswith "$arch" .zip; then unzip -q -d "$destdir" "$arch" "$@" || return elif endswith "$arch" .tgz || endswith "$arch" .tar.gz; then tar xzf "$arch" -C "$destdir" "$@" || return elif endswith "$arch" .tbz2 || endswith "$arch" .tar.bz2; then tar xjf "$arch" -C "$destdir" "$@" || return elif endswith "$arch" .tar; then tar xf "$arch" -C "$destdir" "$@" || return elif endswith "$arch" .jar || endswith "$arch" .war || endswith "$arch" .ear; then ( arch="$(abspath "$arch")" cd "$destdir" jar xf "$arch" "$@" ) || return else return 1 fi } function get_archive_basename() { # Obtenir le nom de base de l'archive $1 local basename="$(basename -- "$1")" # supprimer l'extension basename="${basename%.zip}" basename="${basename%.tgz}" basename="${basename%.tbz2}" basename="${basename%.gz}" basename="${basename%.bz2}" basename="${basename%.tar}" basename="${basename%.jar}" basename="${basename%.war}" basename="${basename%.ear}" # résultat echo "$basename" } function get_archive_appname() { # Obtenir le nom probable de l'application ou du framework contenu dans # l'archive $1, e.g: # get_archive_appname app-0.1.tgz # --> app local appname="$(basename -- "$1")" # supprimer l'extension appname="${appname%.zip}" appname="${appname%.tgz}" appname="${appname%.tbz2}" appname="${appname%.gz}" appname="${appname%.bz2}" appname="${appname%.tar}" appname="${appname%.jar}" appname="${appname%.war}" appname="${appname%.ear}" # supprimer la version et afficher echo "$appname" | awk '{ if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { print substr($0, 1, RSTART - 1) } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { print substr($0, 1, RSTART - 1) } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { # version style AMUE, e.g. 430la print substr($0, 1, RSTART - 1) } else { print $0 } }' } function get_archive_versionsuffix() { # Obtenir la valeur probable de la version de l'application ou du framework # contenu dans l'archive $1, avec le caractère de séparation, e.g: # get_archive_versionsuffix app-0.1.tgz # --> -0.1 local basename="$(get_archive_basename "$1")" echo "$basename" | awk '{ if (match($0, /([-_.][0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { print vs["1"] } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { print vs["1"] } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { # version style AMUE, e.g. 430la print vs["1"] } }' } function get_archive_version() { # Obtenir la valeur probable de la version de l'application ou du framework # contenu dans l'archive $1, e.g: # get_archive_version app-0.1.tgz # --> 0.1 local basename="$(get_archive_basename "$1")" echo "$basename" | awk '{ if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { print vs["1"] } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { print vs["1"] } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { # version style AMUE, e.g. 430la print vs["1"] } }' } function __dump_usernames() { = 500 && $6 ~ /^\/home\// { print $1 }' } function dump_usernames() { # Placer dans le tableau $1 la liste des utilisateurs du système # Cette implémentation consulte /etc/passwd et liste tous les utilisateurs dont # le homedir se trouve dans /home, et dont l'uid est >=500 array_from_lines "${1:-usernames}" "$(__dump_usernames)" } function __resolv_ips() { LANG=C host "$1" 2>/dev/null | awk '/address / { gsub(/^.*address /, ""); print }' } function resolv_ips() { # Placer dans le tableau $1(=ips) la liste des adresses ip correspondant à # l'hôte $2. La résolution est effectuée avec la commande host. array_from_lines "${1:-ips}" "$(__resolv_ips "$2")" } function __resolv_hosts() { LANG=C host "$1" 2>/dev/null | awk '/domain name pointer / { gsub(/^.*domain name pointer /, ""); gsub(/\.$/, ""); print }' } function resolv_hosts() { # Placer dans le tableau $1(=hosts) la liste des hôtes correspondant à # l'adresse ip $2. La résolution est effectuée avec la commande host. array_from_lines "${1:-hosts}" "$(__resolv_hosts "$2")" } function runscript_as() { # Utiliser bash pour lancer le script $2 avec les arguments $3..$n afin qu'il # tourne avec les droits d'un autre user $1(=root). Si $2=exec, utiliser exec # pour lancer le script et ses arguments qui commencent à partir de $3, ce qui # fait que cette fonction ne retourne pas. # Attention! cette fonction ne teste pas avec si on est déjà le user $1. Il y a # donc un risque de boucle infinie si on ne teste pas le user courant. local OENC="$UTF8" local user="${1:-root}"; shift local exec_maybe= if [ "$1" = "exec" ]; then exec_maybe=exec shift fi local cmd cmd="\ __estack=$(qval "$__estack") __tlevel=$(qval "$__tlevel") export __estack __tlevel exec ${BASH:-/bin/sh} $(qvals "$@")" if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then eecho "Entrez le mot de passe de root" $exec_maybe su "$user" -c "$cmd" else if [ "$user" == "root" ]; then $exec_maybe sudo -p "Entrez le mot de passe de %u: " "${BASH:-/bin/sh}" -c "$cmd" else $exec_maybe sudo -p "Entrez le mot de passe de %u: " su "$user" -c "$cmd" fi fi } function runscript_as_root() { # Utiliser bash pour lancer le script $1 avec les arguments $2..$* avec les # droits de root. Si on est déjà en root, le script est simplement lancé. Sinon, # utiliser runscript_as pour lancer le script avec les droits de root. if is_root; then local exec_maybe= if [ "$1" = "exec" ]; then exec_maybe=exec shift fi $exec_maybe "${BASH:-/bin/sh}" "$@" else runscript_as root "$@" fi } function run_as() { # Relancer le script courant afin qu'il tourne avec les droits d'un autre user # $1(=root) # Attention! cette fonction ne teste pas avec si on est déjà ce user. Il y a # donc un risque de boucle infinie si on ne teste pas le user courant. # Il faut lancer cette fonction avec les arguments du script en cours. Par # exemple:: # run_as root "$@" # Si $2=--noexec, on n'utilise pas la fonction exec, ce qui fait que la fonction # retourne. Sinon, on peut considérer que cette fonction ne retourne jamais local user="${1:-root}"; shift local exec_maybe=exec if [ "$1" = "--noexec" ]; then exec_maybe= shift fi runscript_as "$user" $exec_maybe "$0" "$@" } function run_as_root() { # relancer le script courant afin qu'il tourne en root si on est pas en déjà # root. Sinon, cette fonction est un nop. is_root || run_as root "$@" } function check_user() { # Vérifier si le user courant est l'un des users $1..* local user for user in "$@"; do [ "$USER" == "$user" ] && return 0 done return 1 } function ensure_user() { # Vérifier si le user courant est l'un des users $1..N où N est la position du # premier "--". Si ce n'est pas le cas et que l'on est root, relancer le script # avec ce user grâce à la fonction run_as() # Retourner 1 si ce n'était pas le bon user. Retourner 10 si ce n'était pas le # bon user et que l'on n'est pas root (donc impossible à priori de relancer le # script avec le bon user). Retourner 11 si l'utilisateur a choisi de ne pas # lancer le script avec le bon utilisateur # A utiliser de cette manière: # if ensure_user users... -- args; then # # ... on est avec le bon user; faire les opérations # else # # ... ce code n'est exécuté que si une erreur s'est produite, ou si ce # # n'était pas le bon user et que l'option --noexec est utilisée # fi local -a users while [ $# -gt 0 -a "$1" != "--" ]; do array_add users "$1" shift done [ "$1" == "--" ] && shift if ! check_user "${users[@]}"; then if [ ${#users[*]} -gt 1 ]; then ewarn "Cette commande doit être lancée avec l'un des users ${users[*]}" else ewarn "Cette commande doit être lancée avec le user ${users[0]}" fi if ask_yesno "Voulez-vous tenter de relancer la commande avec le bon user?" O; then estep "Lancement du script avec le user ${users[0]}" run_as "${users[0]}" "$@" return 1 elif is_root; then return 11 else return 10 fi fi return 0 } function check_hostname() { # Vérifier si le hostname courant est l'un des hôtes $1..* # localhost matche toujours local userhost user host path for userhost in "$@"; do splitfsep "$userhost" : userhost path splituserhost "$userhost" user host host="${host%%.*}" [ "$host" == localhost -o "$host" == "$MYHOSTNAME" ] && return 0 done return 1 } function check_userhostname() { # Vérifier si le hostname et éventuellement le user courant sont l'un des # arguments $1..* # Chaque argument est de la forme [user@]host, mais le test ne tient compte que # du nom de l'hôte, sans tenir compte du domaine. Si le user n'est pas spécifié, # le test ne porte que sur hostname. local userhost path user host for userhost in "$@"; do if check_hostname "$userhost"; then [[ "$userhost" == *@* ]] || return 0 splitfsep "$userhost" : userhost path splituserhost "$userhost" user host check_user "$user" && return 0 fi done return 1 } UTOOLS_ENSURE_HOSTNAME_SSH_OPTS=() function ensure_hostname() { # Vérifier si le hostname et le user courant sont l'un des arguments $1..* # Chaque argument est de la forme [user@]host, mais le test ne tient compte que # du nom de l'hôte, sans tenir compte du domaine. # Si user est spécifié: # - Si on est sur le bon hôte mais pas le bon user, ensure_user est lancé avec # l'argument approprié pour relancer le script # Si l'argument était de la forme userhost:path, le répertoire courant est # changé avant de lancer le script avec le bon utilisateur. # Sinon (si user n'est pas spécifié): # - Si on n'est pas sur le bon hôte, après confirmation le script est lancé avec # ssh sur l'hôte distant avec le user spécifié (qui vaut par défaut root). Ce # script DOIT exister sur l'hôte distant avec le même chemin. # Si l'argument était de la forme userhost:path, le répertoire courant distant # est changé avant de lancer le script # Si on est avec le bon user sur le bon hôte, le répertoire courant n'est jamais # changé. # Retourner 1 si ce n'était pas le bon user. Retourner 10 si ce n'était pas le # bon user et que l'on n'est pas root (donc impossible à priori de relancer le # script avec le bon user). Retourner 11 si l'utilisateur a choisi de ne pas # lancer le script sur l'hôte distant. Retourner 12 si une erreur s'est produite # avec ssh. # A utiliser de cette manière: # if ensure_hostname user@host... -- args; then # # ... on est [avec le bon user] sur le bon hôte; faire les opérations # else # # ... ce code n'est exécuté que si une erreur s'est produite, ou si ce # # n'était pas le bon user et que l'option --noexec est utilisée # fi local -a userhosts while [ $# -gt 0 -a "$1" != "--" ]; do array_add userhosts "$1" shift done [ "$1" == "--" ] && shift local userhost user host path if ! check_hostname "${userhosts[@]}"; then if [ ${#userhosts[*]} -gt 1 ]; then ewarn "Cette commande n'est valide que sur l'un des hôtes ${userhosts[*]}" else ewarn "Cette commande n'est valide que sur l'hôte ${userhosts[0]}" fi eimportant "Vous pouvez tenter de relancer le script sur ${userhosts[0]}" eimportant "Cela requière que ce script ET les données dont il a besoin soient installés dans la même version et dans le même répertoire sur l'hôte distant. En l'occurence, ce script est accédé par le chemin $script et ce chemin doit exister aussi sur le serveur distant." if ask_yesno "Voulez-vous tenter de relancer le script sur l'hôte distant?" X; then splitfsep "${userhosts[0]}" : userhost path splituserhost "$userhost" user host [ -n "$user" ] || user=root estep "Lancement de la commande sur l'hôte distant $user@$host" local cmd [ -n "$path" ] && cmd="$(qvals cd "$path"); " cmd="$cmd$(qvals "$script" "$@")" ssh -qt "${UTOOLS_ENSURE_HOSTNAME_SSH_OPTS[@]}" "$user@$host" "$cmd" [ $? -eq 255 ] && return 12 return 1 else return 11 fi fi # nous sommes sur le bon hôte. Si user est spécifié, le vérifier aussi local userhost user host for userhost in "${userhosts[@]}"; do [[ "$userhost" == *@* ]] || continue if check_hostname "$userhost"; then splitfsep "$userhost" : userhost path splituserhost "$userhost" user host [ -n "$path" ] && cd "$path" ensure_user "$user" -- "$@" return $? fi done return 0 } __AWKDEF_FUNCTIONS=' function num(s) { if (s ~ /^[0-9]+$/) return int(s) else return s } function ord(s, i) { s = substr(s, 1, 1) i = index(" !\"#$%&'\''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", s) if (i != 0) i += 32 - 1 return i } function hex(i, s) { s = sprintf("%x", i) if (length(s) < 2) s = "0" s return s } function qhtml(s) { gsub(/&/, "\\&", s) gsub(/"/, "\\"", s) gsub(/>/, "\\>", s) gsub(/", s) gsub(/"/, "\"", s) gsub(/&/, "\\&", s) return s } function qawk(s) { gsub(/\\/, "\\\\", s) gsub(/"/, "\\\"", s) gsub(/\n/, "\\n", s) return "\"" s "\"" } function qval(s) {'" gsub(/'/, \"'\\\\''\", s) return \"'\" s \"'\" "'} function sqval(s) { return " " qval(s) } function qvals( i, line) { line = "" for (i = 1; i <= NF; i++) { if (i > 1) line = line " " line = line qval($i) } return line } function sqvals() { return " " qvals() } function qarr(values, prefix, i, count, line) { line = prefix count = array_len(values) for (i = 1; i <= count; i++) { if (i > 1 || line != "") line = line " " line = line qval(values[i]) } return line } function qregexp(s) { gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) return s } function qsubrepl(s) { gsub(/\\/, "\\\\", s) gsub(/&/, "\\\\&", s) return s } function qgrep(s) { gsub(/[[\\.^$*]/, "\\\\&", s) return s } function qegrep(s) { gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) return s } function qsql(s, suffix) {'" gsub(/'/, \"''\", s) return \"'\" s \"'\" (suffix != \"\"? \" \" suffix: \"\") "'} function cqsql(s, suffix) { return "," qsql(s, suffix) } function unquote_mysqlcsv(s) { gsub(/\\n/, "\n", s) gsub(/\\t/, "\t", s) gsub(/\\0/, "\0", s) gsub(/\\\\/, "\\", s) return s } function sval(s) { if (s == "") return s else return " " s } function cval(s, suffix) { if (s == "") return s else return "," s (suffix != ""? " " suffix: "") } # aliases pour compatibilité function quote_html(s) { return qhtml(s) } function quote_value(s) { return qval(s) } function qsval(s) { return sqval(s) } function quoted_values() { return qvals() } function qsvals(s) { return sqvals(s) } function quote_regexp(s) { return qregexp(s) } function quote_subrepl(s) { return qsubrepl(s) } function quote_grep(s) { return qgrep(s) } function quote_egrep(s) { return qegrep(s) } function quote_sql(s) { return qsql(s) } function __parse_date_fr(date, parts, y, m, d) { if (match(date, /([0-9][0-9]?)\/([0-9][0-9]?)\/([0-9][0-9][0-9][0-9])/, parts)) { y = int(parts[3]) m = int(parts[2]) d = int(parts[1]) return mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)) } return -1 } function __parse_date_mysql(date, parts, y, m, d) { if (match(date, /([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])/, parts)) { y = int(parts[1]) m = int(parts[2]) d = int(parts[3]) return mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)) } return -1 } function __parse_date_any(date, serial) { serial = __parse_date_fr(date) if (serial == -1) serial = __parse_date_mysql(date) return serial } function date_serial(date) { return __parse_date_any(date) } function date_parse(date, serial) { serial = __parse_date_any(date) if (serial == -1) return date return strftime("%d/%m/%Y", serial) } function date_monday(date, serial, dow) { serial = __parse_date_any(date) if (serial == -1) return date dow = strftime("%u", serial) serial -= (dow - 1) * 86400 return strftime("%d/%m/%Y", serial) } function date_add(date, nbdays, serial) { serial = __parse_date_any(date) if (serial == -1) return date serial += nbdays * 86400 return strftime("%d/%m/%Y", serial) } function mkindices(values, indices, i, j) { array_new(indices) j = 1 for (i in values) { indices[j++] = int(i) } return asort(indices) } function array_new(dest) { dest[0] = 0 # forcer awk à considérer dest comme un tableau delete dest } function array_newsize(dest, size, i) { dest[0] = 0 # forcer awk à considérer dest comme un tableau delete dest size = int(size) for (i = 1; i <= size; i++) { dest[i] = "" } } function array_len(values, count, i) { # length(array) a un bug sur awk 3.1.5 # cette version est plus lente mais fonctionne toujours count = 0 for (i in values) { count++ } return count } function array_copy(dest, src, count, indices, i) { array_new(dest) count = mkindices(src, indices) for (i = 1; i <= count; i++) { dest[indices[i]] = src[indices[i]] } } function array_getlastindex(src, count, indices) { count = mkindices(src, indices) if (count == 0) return 0 return indices[count] } function array_add(dest, value, lastindex) { lastindex = array_getlastindex(dest) dest[lastindex + 1] = value } function array_deli(dest, i, l) { i = int(i) if (i == 0) return l = array_len(dest) while (i < l) { dest[i] = dest[i + 1] i++ } delete dest[l] } function array_del(dest, value, ignoreCase, i) { do { i = key_index(value, dest, ignoreCase) if (i != 0) array_deli(dest, i) } while (i != 0) } function array_extend(dest, src, count, lastindex, indices, i) { lastindex = array_getlastindex(dest) count = mkindices(src, indices) for (i = 1; i <= count; i++) { dest[lastindex + i] = src[indices[i]] } } function array_fill(dest, i) { array_new(dest) for (i = 1; i <= NF; i++) { dest[i] = $i } } function array_getline(src, count, indices, i, j) { $0 = "" count = mkindices(src, indices) for (i = 1; i <= count; i++) { j = indices[i] $j = src[j] } } function array_appendline(src, count, indices, i, nf, j) { count = mkindices(src, indices) nf = NF for (i = 1; i <= count; i++) { j = nf + indices[i] $j = src[indices[i]] } } function in_array(value, values, ignoreCase, i) { if (ignoreCase) { value = tolower(value) for (i in values) { if (tolower(values[i]) == value) return 1 } } else { for (i in values) { if (values[i] == value) return 1 } } return 0 } function key_index(value, values, ignoreCase, i) { if (ignoreCase) { value = tolower(value) for (i in values) { if (tolower(values[i]) == value) return int(i) } } else { for (i in values) { if (values[i] == value) return int(i) } } return 0 } function array2s(values, prefix, sep, suffix, noindices, first, i, s) { if (!prefix) prefix = "[" if (!sep) sep = ", " if (!suffix) suffix = "]" s = prefix first = 1 for (i in values) { if (first) first = 0 else s = s sep if (!noindices) s = s "[" i "]=" s = s values[i] } s = s suffix return s } function array2so(values, prefix, sep, suffix, noindices, count, indices, i, s) { if (!prefix) prefix = "[" if (!sep) sep = ", " if (!suffix) suffix = "]" s = prefix count = mkindices(values, indices) for (i = 1; i <= count; i++) { if (i > 1) s = s sep if (!noindices) s = s "[" indices[i] "]=" s = s values[indices[i]] } s = s suffix return s } function array_join(values, sep, prefix, suffix, count, indices, i, s) { s = prefix count = mkindices(values, indices) for (i = 1; i <= count; i++) { if (i > 1) s = s sep s = s values[indices[i]] } s = s suffix return s } function printto(s, output) { if (output == "") { print s } else if (output ~ /^>>/) { sub(/^>>/, "", output) print s >>output } else if (output ~ /^>/) { sub(/^>/, "", output) print s >output # XXX désactivé pour le moment parce que affiche une erreur de syntaxe si # utilisé avec une version de awk autre que gnuawk #} else if (output ~ /^\|&/) { # sub(/^\|&/, "", output) # print s |&output #} else if (output ~ /^\|/) { # sub(/^\|/, "", output) # print s |output } else { print s >output } } function find_line(input, field, value, orig, line) { orig = $0 line = "" while ((getline 0) { if ($field == value) { line = $0 break } } close(input) $0 = orig return line } function merge_line(input, field, key, line) { line = find_line(input, field, $key) if (line != "") $0 = $0 FS line } function __csv_parse_quoted(line, destl, colsep, qchar, echar, pos, tmpl, nextc, resl) { line = substr(line, 2) resl = "" while (1) { pos = index(line, qchar) if (pos == 0) { # chaine mal terminee resl = resl line destl[0] = "" destl[1] = 0 return resl } if (echar != "" && pos > 1) { # tenir compte du fait qu"un caratère peut être mis en échappement prevc = substr(line, pos - 1, 1) quotec = substr(line, pos, 1) nextc = substr(line, pos + 1, 1) if (prevc == echar) { # qchar en échappement tmpl = substr(line, 1, pos - 2) resl = resl tmpl quotec line = substr(line, pos + 1) continue } tmpl = substr(line, 1, pos - 1) if (nextc == colsep || nextc == "") { # fin de champ ou fin de ligne resl = resl tmpl destl[0] = substr(line, pos + 2) destl[1] = nextc == colsep return resl } else { # erreur de syntaxe: guillemet non mis en échappement # ignorer cette erreur et prendre le guillemet quand meme resl = resl tmpl quotec line = substr(line, pos + 1) } } else { # pas d"échappement pour qchar. il est éventuellement doublé tmpl = substr(line, 1, pos - 1) quotec = substr(line, pos, 1) nextc = substr(line, pos + 1, 1) if (nextc == colsep || nextc == "") { # fin de champ ou fin de ligne resl = resl tmpl destl[0] = substr(line, pos + 2) destl[1] = nextc == colsep return resl } else if (nextc == qchar) { # qchar en echappement resl = resl tmpl quotec line = substr(line, pos + 2) } else { # erreur de syntaxe: guillemet non mis en échappement # ignorer cette erreur et prendre le guillemet quand meme resl = resl tmpl quotec line = substr(line, pos + 1) } } } } function __csv_parse_unquoted(line, destl, colsep, qchar, echar, pos) { pos = index(line, colsep) if (pos == 0) { destl[0] = "" destl[1] = 0 return line } else { destl[0] = substr(line, pos + 1) destl[1] = 1 return substr(line, 1, pos - 1) } } function __array_parsecsv(fields, line, nbfields, colsep, qchar, echar, shouldparse, destl, i) { array_new(fields) array_new(destl) i = 1 shouldparse = 0 # shouldparse permet de gérer le cas où un champ vide est en fin de ligne. # en effet, après "," il faut toujours parser, même si line=="" while (shouldparse || line != "") { if (index(line, qchar) == 1) { value = __csv_parse_quoted(line, destl, colsep, qchar, echar) line = destl[0] shouldparse = destl[1] } else { value = __csv_parse_unquoted(line, destl, colsep, qchar, echar) line = destl[0] shouldparse = destl[1] } fields[i] = value i = i + 1 } if (nbfields) { nbfields = int(nbfields) i = array_len(fields) while (i < nbfields) { i++ fields[i] = "" } } return array_len(fields) } BEGIN { DEFAULT_COLSEP = "," DEFAULT_QCHAR = "\"" DEFAULT_ECHAR = "" } function array_parsecsv2(fields, line, nbfields, colsep, qchar, echar) { return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) } function array_parsecsv(fields, line, nbfields, colsep, qchar, echar) { if (colsep == "") colsep = DEFAULT_COLSEP if (qchar == "") qchar = DEFAULT_QCHAR if (echar == "") echar = DEFAULT_ECHAR return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) } function parsecsv(line, fields) { array_parsecsv(fields, line) array_getline(fields) return NF } function getlinecsv(file, fields) { if (file) { getline 1) line = line colsep if (qchar != "" && index(value, qchar) != 0) { if (echar != "") gsub(qchar, quote_subrepl(echar) "&", value); else gsub(qchar, "&&", value); } if (qchar != "" && (index(value, mvsep) != 0 || index(value, colsep) != 0 || index(value, qchar) != 0 || __csv_should_quote(value))) { line = line qchar value qchar } else { line = line value } } return line } function array_formatcsv(fields) { return array_formatcsv2(fields, ",", ";", "\"", "") } function array_printcsv(fields, output) { printto(array_formatcsv(fields), output) } function get_formatcsv( fields) { array_fill(fields) return array_formatcsv(fields) } function formatcsv() { $0 = get_formatcsv() } function printcsv(output, fields) { array_fill(fields) array_printcsv(fields, output) } function array_findcsv(fields, input, field, value, nbfields, orig, found, i) { array_new(orig) array_fill(orig) array_new(fields) found = 0 while ((getline 0) { array_parsecsv(fields, $0, nbfields) if (fields[field] == value) { found = 1 break } } close(input) array_getline(orig) if (!found) { delete fields if (nbfields) { nbfields = int(nbfields) i = array_len(fields) while (i < nbfields) { i++ fields[i] = "" } } } return found } function __and(var, x, l_res, l_i) { l_res=0; for (l_i=0; l_i < 8; l_i++){ if (var%2 == 1 && x%2 == 1) l_res=l_res/2 + 128; else l_res/=2; var=int(var/2); x=int(x/2); } return l_res; } # Rotate bytevalue left x times function __lshift(var, x) { while(x > 0){ var*=2; x--; } return var; } # Rotate bytevalue right x times function __rshift(var, x) { while(x > 0){ var=int(var/2); x--; } return var; } BEGIN { __BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" } function b64decode(src, result, base1, base2, base3, base4) { result = "" while (length(src) > 0) { # Specify byte values base1 = substr(src, 1, 1) base2 = substr(src, 2, 1) base3 = substr(src, 3, 1); if (base3 == "") base3 = "=" base4 = substr(src, 4, 1); if (base4 == "") base4 = "=" # Now find numerical position in BASE64 string byte1 = index(__BASE64, base1) - 1 if (byte1 < 0) byte1 = 0 byte2 = index(__BASE64, base2) - 1 if (byte2 < 0) byte2 = 0 byte3 = index(__BASE64, base3) - 1 if (byte3 < 0) byte3 = 0 byte4 = index(__BASE64, base4) - 1 if (byte4 < 0) byte4 = 0 # Reconstruct ASCII string result = result sprintf( "%c", __lshift(__and(byte1, 63), 2) + __rshift(__and(byte2, 48), 4) ) if (base3 != "=") result = result sprintf( "%c", __lshift(__and(byte2, 15), 4) + __rshift(__and(byte3, 60), 2) ) if (base4 != "=") result = result sprintf( "%c", __lshift(__and(byte3, 3), 6) + byte4 ) # Decrease incoming string with 4 src = substr(src, 5) } return result } ' function awkdef() { # Afficher un script à insérer au début d'un script awk. Ce script définit dans # une section BEGIN{} les variables donnés en arguments, et avec l'option -f, # des fonctions utiles. Si une valeur ne ressemble pas à une définition de # variable, l'analyse des variables s'arrête et le reste des arguments est # inséré tel quel. Cette fonction peut être utilisée de cette manière: # awk "$(awkdef -f var=value... 'script awk')" # Normalement, les variables définies sont scalaires, avec une syntaxe de la # forme var[:type]=value. type peut valoir str ou int, pour forcer le type de la # variable créée dans awk. # Il est possible d'utiliser la syntaxe awk_array[@]=bash_array ou array[@] (qui # est équivalente à array[@]=array) pour initialiser le tableau awk_array, qui # contiendra toute les valeurs du tableau nommé bash_array, avec les indices de # 1 à N, N étant le nombre d'éléments du tableau bash_array. La variable # awk_array_count est aussi initialisée, et contient le nombre d'éléments du # tableau # La syntaxe "awk_array[@]=<\n..." permet de spécifier les valeurs du tableau, # une par ligne, e.g: # $'values[@]=<\nvalue1\nvalue2' # pour un tableau values qui contiendra deux valeurs: value1 et value2 # Avec l'option -f, des fonctions supplémentaires sont définies. Elles sont # décrites dans le module awk. [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_AWKDEF_SET_X=1; } # désactiver set -x pour cette fonction if [ "${1:0:3}" == "-f" ]; then shift echo "$__AWKDEF_FUNCTIONS" fi if [ $# -gt 0 ]; then local __ad_arg __ad_vpos __ad_name __ad_value echo "BEGIN {" while [ -n "${1:0:1}" ]; do # considérer seulement les 256 premiers caractères. en effet, si # l'argument a une taille trop importante, il y a des délais # insupportables __ad_arg="${1:0:256}" local __ad_array= if [ "${__ad_arg%\[@\]}" != "$__ad_arg" ]; then __ad_array=1 __ad_name="${__ad_arg%\[@\]}" [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break __ad_value="$__ad_name" elif [[ "$__ad_arg" == *\[@\]=* ]]; then __ad_array=1 __ad_name="${__ad_arg%%\[@\]=*}" [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break __ad_vpos=$((${#__ad_name} + 4)) __ad_value="${1:$__ad_vpos}" [ ${#__ad_value} -ne 0 ] || __ad_value="$__ad_name" elif [[ "$__ad_arg" == *=* ]]; then local __ad_int= __ad_str= __ad_name="${__ad_arg%%=*}" __ad_vpos=$((${#__ad_name} + 1)) if [ "${__ad_name%:int}" != "$__ad_name" ]; then __ad_int=1 __ad_name="${__ad_name%:int}" elif [ "${__ad_name%:str}" != "$__ad_name" ]; then __ad_str=1 __ad_name="${__ad_name%:str}" fi [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break __ad_value="${1:$__ad_vpos}" if [ -n "$__ad_int" ]; then # valeur entière echo "$__ad_name = int($(qawk "$__ad_value") + 0)" elif [ -n "$__ad_str" ]; then # valeur chaine echo "$__ad_name = $(qawk "$__ad_value")" elif [ ${#__ad_value} -lt 256 ] && isnum "$__ad_value"; then # considérer qu'une nombre fait moins de 256 chiffres echo "$__ad_name = $__ad_value" else echo "$__ad_name = $(qawk "$__ad_value")" fi else # fin de l'analyse des définitions de variable break fi if [ -n "$__ad_array" ]; then if [ "${__ad_value:0:2}" == $'<\n' ]; then local -a __ad_values array_from_lines __ad_values "${__ad_value:2}" __ad_value=__ad_values fi __ad_value="${__ad_value}[@]" local __ad_i=1 echo "$__ad_name[0] = 0; delete $__ad_name" for __ad_arg in "${!__ad_value}"; do echo "$__ad_name[$__ad_i]=$(qawk "$__ad_arg")" __ad_i=$(($__ad_i + 1)) done eval "echo \"\${__ad_name}_count = \${#$__ad_value}\"" fi shift done echo "}" for __ad_arg in "$@"; do recho "$__ad_arg" done fi [ -n "$__ULIB_AWKDEF_SET_X" ] && set -x; return 0 } function lawkrun() { # wrapper pour lancer awk avec un script préparé par awkdef. Les définitions et # les arguments sont séparés par --, e.g. # awkrun var0=value0 var1=value1 script -- input0 input1 local -a __ar_defs __ar_args while [ $# -gt 0 -a "$1" != "--" ]; do __ar_defs=("${__ar_defs[@]}" "$1") shift done shift while [ $# -gt 0 ]; do __ar_args=("${__ar_args[@]}" "$1") shift done local __ar_script="$(awkdef "${__ar_defs[@]}")" [ -n "$UTOOLS_AWKRUN_DEBUG" ] && estep "Script awkrun: $__ar_script" #pgawk -p "$__ar_script" "${__ar_args[@]}" awk "$__ar_script" "${__ar_args[@]}" } function cawkrun() { LANG=C lawkrun "$@"; } function awkrun() { LANG=C lawkrun "$@"; } function __lf_get_age() { local y=$(date "+%Y") local dy=$(date "+%j"); while [ "${dy#0}" != "$dy" ]; do dy="${dy#0}"; done [ -n "$dy" ] || dy=0 local h=$(date "+%H"); while [ "${h#0}" != "$h" ]; do h="${h#0}"; done [ -n "$h" ] || h=0 echo $((($y * 365 + $dy) * 24 + $h)) } function lf_trylock() { # USAGE # lf_trylock [-h max_hours] /path/to/lockfile # OPTIONS # lockfile # fichier qui doit contenir le verrou # -h max_hours # Nombre d'heures (par défaut 4) au bout duquel afficher stale # Sinon, afficher locked # Retourne 0 si le verrou a été placé correctement. Il ne faut pas oublier de # supprimer le fichier. Le mieux est de le faire supprimer automatiquement par # autoclean: # lockfile=... # case "$(lf_trylock "$lockfile")" in # locked) ...;; # stale) ...;; # esac # autoclean "$lockfile" # Sinon, retourner 1 et afficher l'une des deux valeurs suivantes: # - stale si le verrou a déjà été placé, depuis au moins max_hours heures # - locked si le verrou a déjà été placé # - retry si une erreur s'est produite pendant la pose du verrou ou sa # lecture. Cela peut se produire si les droits ne sont pas suffisants pour # écrire dans le répertoire destination, ou si le fichier a été supprimé # avant sa lecture (race-condition). Dans ce dernier cas, reessayer permettra # d'acquérir le verrou local eoo lockfile max_hours=4 while [ -n "$1" ]; do case "$1" in -h) shift; max_hours="$1";; --) shift; eoo=1;; *) eoo=1;; esac [ -n "$eoo" ] && break shift done lockfile="$1" [ -n "$lockfile" ] || die "il faut spécifier un fichier pour le verrou" local now="$(__lf_get_age)" if (set -C; echo "$now" >"$lockfile") 2>/dev/null; then return 0 fi local prev diff if prev="$(<"$lockfile")"; then diff="$(($now - $prev))" if [ "$diff" -gt "$max_hours" ]; then echo stale else echo locked fi elif [ -f "$lockfile" ]; then echo retry fi return 1 } function pidfile_set() { # USAGE # pidfile_set [-p pid] /path/to/pidfile # OPTIONS # pidfile # fichier qui doit contenir le pid du script # -p pid # spécifier le pid. par défaut, utiliser $$ # -r si pidfile existe mais que le processus ne tourne plus, faire # comme si le fichier n'existe pas. # Retourner 0 si le pid a été correctement écrit dans le fichier. Ce fichier # sera supprimé automatiquement en fin de script # Retourner 1 si le fichier existe déjà et que le processus est en train de # tourner. # Retourner 2 si le fichier existe déjà mais que le processus ne tourne plus. # Retourner 10 si autre erreur grave s'est produite (par exemple, s'il manque le # chemin vers pidfile, ou si le fichier n'est pas accessible en écriture.) local eoo pidfile pid=$$ replace= while [ -n "$1" ]; do case "$1" in -p) shift pid="$1" ;; -r) replace=1 ;; --) shift eoo=1 ;; *) eoo=1 ;; esac [ -n "$eoo" ] && break shift done pidfile="$1" [ -n "$pidfile" ] || return 10 if [ -f "$pidfile" ]; then local curpid="$(<"$pidfile")" if is_running "$curpid"; then return 1 elif [ -n "$replace" ]; then /bin/rm -f "$pidfile" || return 10 else return 2 fi fi echo_ "$pid" >"$pidfile" || return 10 autoclean "$pidfile" return 0 } function pidfile_check() { # USAGE # pidfile_check /path/to/pidfile # OPTIONS # pidfile # fichier qui doit contenir le pid d'un processus # Cette fonction permet de vérifier si le processus associé à un fichier de pid # est en train de tourner. # Retourner 0 si le fichier de pid existe et que le process du pid spécifié est # en train de tourner. Retourner 1 sinon. # Retourner 10 si erreur grave s'est produite (par exemple, s'il manque le # chemin vers pidfile, ou si le fichier n'est pas accessible en lecture.) local pidfile="$1" [ -n "$pidfile" ] || return 10 if [ -f "$pidfile" ]; then [ -r "$pidfile" ] || return 10 local pid="$(<"$pidfile")" is_running "$pid" && return 0 fi return 1 } function page_maybe() { # Utiliser less, si possible, pour afficher le flux en entrée. Si le terminal # n'est pas interactif ou si le nombre de lignes en entrée est inférieur au # nombre de lignes du terminal, afficher simplement le flux. # Les arguments de cette fonction sont passés à less if isatty; then less -XF "$@" else cat fi } ################################################################################ ## entrée/sortie function utools_local() { # Afficher les commandes pour rendre locales certaines variables en fonction des # arguments: # - opts rend locale args, pour utiliser parse_opts() à l'intérieur d'une # fonction. # - verbosity et interaction rendent respectivement locales __verbosity et # __interaction. Ceci est utile pour pouvoir appeler sans risque de pollution # de l'environnement une fonction qui utilise parse_opts() avec les # définitions de PRETTYOPTS. # Si aucun arguments n'est fourni, toutes les définitions sont affichées. local arg [ $# -gt 0 ] || set -- opts verbosity interaction for arg in "$@"; do case "$arg" in parse_opts|opts|o|args) echo "local -a args";; verbosity|v) echo "local __verbosity='$__verbosity'";; interaction|i) echo "local __interaction='$__interaction'";; esac done } function stdredir() { # Lancer la commande $4..@ en redirigeant stdin depuis $1, stdout vers $2, # stderr vers $3. Si $1 est vide ou vaut /dev/stdin, la redirection n'est # pas faite. Si $2 est vide ou vaut /dev/stdout, la redirection n'est pas # faite. Si $3 est vide ou vaut /dev/stderr, la redirection n'est pas faite. # Cette fonction existe parce que sur certaines versions de bash, il semble # que les redirections /dev/std* ne sont pas traitées de façon particulière. # De plus, sur des technologies telles que OpenVZ, les chemins /dev/std* ne # sont pas créés (parce que /proc/self/fd/* n'est pas accessible). Donc, # dans de rares cas où le script tourne sur OpenVZ avec une version de bash # qui est buggée, la redirection n'est pas faite correctement. local __redirs __in __out __err if [ -n "$1" -o "$1" == /dev/stdin ]; then if [ "${1#<}" != "$1" ]; then __in="${1#<}" else __in="$1" fi __redirs="$__redirs"' <"$__in"' fi; shift if [ -n "$1" -o "$1" == /dev/stdout ]; then if [ "${1#>>}" != "$1" ]; then __out="${1#>>}" __redirs="$__redirs"' >>"$__out"' elif [ "${1#>}" != "$1" ]; then __out="${1#>}" __redirs="$__redirs"' >"$__out"' else __out="$1" __redirs="$__redirs"' >"$__out"' fi fi; shift if [ -n "$1" -o "$1" == /dev/stderr ]; then if [ "${1#>>}" != "$1" ]; then __err="${1#>>}" __redirs="$__redirs"' 2>>"$__err"' elif [ "${1#>}" != "$1" ]; then __err="${1#>}" __redirs="$__redirs"' 2>"$__err"' else __err="$1" __redirs="$__redirs"' 2>"$__err"' fi fi; shift eval '"$@"'"$__redirs" } function isatty() { # tester si STDOUT n'est pas une redirection tty -s <&1 } function in_isatty() { # tester si STDIN n'est pas une redirection tty -s } function out_isatty() { # tester si STDOUT n'est pas une redirection tty -s <&1 } function err_isatty() { # tester si STDERR n'est pas une redirection tty -s <&2 } function die() { [ $# -gt 0 ] && eerror "$@"; exit 1; } function exit_with { if [ $# -gt 0 ]; then "$@"; fi; exit $?; } function die_with { [ $# -gt 0 ] && eerror "$1"; shift; [ $# -gt 0 ] && "$@"; exit 1; } function die_unless() { # Afficher $-1 et quitter le script avec die() si la commande $1..-2 retourne # FAUX local count=$# if [ $count -eq 0 ]; then exit 1 elif [ $count -eq 1 ]; then "$@" || exit $? else local m r m="${@:$count}" count=$(($count - 1)) set -- "${@:1:$count}" if "$@"; then : else r=$? eerror "$m" exit $r fi fi } function eerror_unless() { # Afficher $-1 avec eerror() et retourner $? si la commande $1..-2 retourne FAUX local count=$# if [ $count -eq 0 ]; then return 1 elif [ $count -eq 1 ]; then "$@" || return $? else local m r m="${@:$count}" count=$(($count - 1)) set -- "${@:1:$count}" if "$@"; then : else r=$? eerror "$m" return $r fi fi } function die_if() { # Afficher $-1 et quitter le script avec die() si la commande $1..-2 retourne # VRAI local count=$# if [ $count -eq 0 ]; then : elif [ $count -eq 1 ]; then "$@" && exit 1 else local m r m="${@:$count}" count=$(($count - 1)) set -- "${@:1:$count}" if "$@"; then eerror "$m" exit 1 fi fi } function eerror_if() { # Afficher $-1 avec eerror() et retourner le code d'erreur 1 si la commande # $1..-2 retourne VRAI local count=$# if [ $count -eq 0 ]; then : elif [ $count -eq 1 ]; then "$@" && return 1 else local m r m="${@:$count}" count=$(($count - 1)) set -- "${@:1:$count}" if "$@"; then eerror "$m" return 1 fi fi } function noerror() { # lancer la commande "$@" et masquer son code de retour [ $# -gt 0 ] || set : "$@" || return 0 } function noout() { # lancer la commande "$@" en supprimant sa sortie standard [ $# -gt 0 ] || return 0 "$@" >/dev/null } function noerr() { # lancer la commande "$@" en supprimant sa sortie d'erreur [ $# -gt 0 ] || return 0 "$@" 2>/dev/null } TAB=$'\t' LATIN1=iso-8859-1 LATIN9=iso-8859-15 UTF8=utf-8 OENC="$UTF8" if ! progexists iconv; then function iconv() { cat; } fi function __lang_encoding() { local lang="$(<<<"$LANG" awk '{ print tolower($0) }')" case "$lang" in *@euro) echo "iso-8859-15";; *.utf-8|*.utf8) echo "utf-8";; *) echo "iso-8859-1";; esac } function __norm_encoding() { awk '{ enc = tolower($0) gsub(/^latin$/, "latin1", enc) gsub(/^latin1$/, "iso-8859-1", enc) gsub(/^latin9$/, "iso-8859-15", enc) gsub(/[-_]/, "", enc) if (enc == "iso8859" || enc == "iso88591" || enc == "8859" || enc == "88591") print "iso-8859-1" else if (enc == "iso885915" || enc == "885915") print "iso-8859-15" else if (enc == "utf" || enc == "utf8") print "utf-8" else print $0 }' <<<"$1" } function __init_encoding() { local DEFAULT_ENCODING="$(__lang_encoding)" [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 [ -n "$UTOOLS_OUTPUT_ENCODING" ] || UTOOLS_OUTPUT_ENCODING="$DEFAULT_ENCODING" UTOOLS_OUTPUT_ENCODING="$(__norm_encoding "$UTOOLS_OUTPUT_ENCODING")" [ -n "$UTOOLS_INPUT_ENCODING" ] || UTOOLS_INPUT_ENCODING="$UTOOLS_OUTPUT_ENCODING" UTOOLS_INPUT_ENCODING="$(__norm_encoding "$UTOOLS_INPUT_ENCODING")" [ -n "$UTOOLS_EDITOR_ENCODING" ] || UTOOLS_EDITOR_ENCODING="$UTOOLS_INPUT_ENCODING" UTOOLS_EDITOR_ENCODING="$(__norm_encoding "$UTOOLS_EDITOR_ENCODING")" IENC="$UTOOLS_INPUT_ENCODING" OENC="$UTOOLS_OUTPUT_ENCODING" } if [ -n "$UTOOLS_LANG" -a -z "$LANG" ]; then export UTOOLS_LANG export LANG="$UTOOLS_LANG" fi __init_encoding function tooenc() { # Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie # $3=($UTOOLS_OUTPUT_ENCODING) local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" if [ "$from" == "$to" ]; then recho "$src" else iconv -f "$from" -t "$to" <<<"$src" fi } function uecho() { tooenc "$*" } function tooenc_() { # Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie # $3=($UTOOLS_OUTPUT_ENCODING) local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" if [ "$from" == "$to" ]; then recho_ "$src" else recho_ "$src" | iconv -f "$from" -t "$to" fi } function uecho_() { tooenc_ "$*" } function toienc() { # Transformer la valeur $1 de $2(=$IENC) vers l'encoding d'entrée # $3(=$UTOOLS_INPUT_ENCODING) local __tie_var="$1" __tie_to="${2:-$IENC}" __tie_from="${3:-$UTOOLS_INPUT_ENCODING}" if [ "$__tie_from" != "$__tie_to" ]; then _setv "$__tie_var" "$(iconv -f "$__tie_from" -t "$__tie_to" <<<"${!__tie_var}")" fi } function uread() { # Lire une valeur sur stdin et la placer dans la variable $1. On assume que la # valeur en entrée est encodée dans l'encoding d'entrée par défaut [ $# -gt 0 ] || set -- REPLY local __r_var read "$@" for __r_var in "$@"; do [ -z "$__r_var" -o "${__r_var:0:1}" == "-" ] && continue # ignorer les options toienc "$__r_var" done } function stooenc() { # Transformer la valeur lue sur stdin de $OENC vers l'encoding de sortie par # défaut ($UTOOLS_OUTPUT_ENCODING) local from="${1:-$OENC}" to="${2:-$UTOOLS_OUTPUT_ENCODING}" if [ "$from" == "$to" ]; then cat else iconv -f "$from" -t "$to" fi } function stoienc() { # Transformer la valeur lue sur stdin de $IENC vers l'encoding d'entrée par # défaut ($UTOOLS_INPUT_ENCODING) local to="${1:-$IENC}" from="${2:-$UTOOLS_INPUT_ENCODING}" if [ "$from" == "$to" ]; then cat else iconv -f "$from" -t "$to" fi } # faut-il dater les messages de etitle, estep, ebegin? # Faire UTOOLS_EDATE=1 en début de script pour activer cette fonctionnalité export UTOOLS_EDATE function __edate() { [ -n "$UTOOLS_EDATE" ] && date +"[%d/%m/%Y-%H:%M:%S] "; } export UTOOLS_ELOG_OVERWRITE function __set_no_colors() { :; } function elogto() { # Activer UTOOLS_EDATE et rediriger STDOUT et STDERR vers le fichier $1 # Si deux fichiers sont spécifiés, rediriger STDOUT vers $1 et STDERR vers $2 # Si aucun fichier n'est spécifié, ne pas faire de redirection # Si la redirection est activée, forcer l'utilisation de l'encoding UTF8 # Si UTOOLS_ELOG_OVERWRITE=1, alors le fichier en sortie est écrasé. Sinon, les # lignes en sortie lui sont ajoutées UTOOLS_EDATE=1 if [ -n "$1" -a -n "$2" ]; then LANG=fr_FR.UTF8 UTOOLS_OUTPUT_ENCODING="$UTF8" __set_no_colors 1 if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then exec >"$1" 2>"$2" else exec >>"$1" 2>>"$2" fi elif [ -n "$1" ]; then LANG=fr_FR.UTF8 UTOOLS_OUTPUT_ENCODING="$UTF8" __set_no_colors 1 if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then exec >"$1" 2>&1 else exec >>"$1" 2>&1 fi fi } # variables utilisées pour l'affichage indenté des messages et des titres # __estack est la liste des invocations de 'ebegin' et 'etitle' en cours # __tlevel est l'indentation à appliquer avant d'afficher le message export __estack __tlevel function __indent() { # indenter les lignes de $1, sauf la première if [ "${1/ /}" != "$1" ]; then sed "2,\$s/^/${__tlevel}/g" <<<"$1" else recho "$1" fi } # fonctions à surcharger pour modifier la façon dont les messages sont affichés function __eerror() { tooenc "$(__edate)${__tlevel}ERROR $(__indent "$1")"; } function __ewarn() { tooenc "$(__edate)${__tlevel}WARNING $(__indent "$1")"; } function __enote() { tooenc "$(__edate)${__tlevel}NOTE $(__indent "$1")"; } function __ebanner() { local maxi="${COLUMNS:-80}" local -a lines local psfix line psfix="$(__edate)${__tlevel}" while [ ${#psfix} -lt $maxi ]; do psfix="$psfix="; done tooenc "$psfix" maxi=$(($maxi - 1)) array_from_xlines lines "$1" for line in "" "${lines[@]}" ""; do line="$(__edate)${__tlevel}= $line" if [ ${#line} -le $maxi ]; then while [ ${#line} -lt $maxi ]; do line="$line "; done line="$line=" fi tooenc "$line" done tooenc "$psfix" } function __eimportant() { tooenc "$(__edate)${__tlevel}IMPORTANT $(__indent "$1")"; } function __eattention() { tooenc "$(__edate)${__tlevel}ATTENTION $(__indent "$1")"; } function __einfo() { tooenc "$(__edate)${__tlevel}INFO $(__indent "$1")"; } function __eecho() { tooenc "$(__edate)${__tlevel}$(__indent "$1")"; } function __eecho_() { tooenc_ "$(__edate)${__tlevel}$(__indent "$1")"; } function __edebug() { tooenc "$(__edate)${__tlevel}DEBUG $(__indent "$1")"; } function __estep() { tooenc "$(__edate)${__tlevel}. $(__indent "$1")"; } function __estepe() { tooenc "$(__edate)${__tlevel}.E $(__indent "$1")"; } function __estepw() { tooenc "$(__edate)${__tlevel}.W $(__indent "$1")"; } function __estepn() { tooenc "$(__edate)${__tlevel}.N $(__indent "$1")"; } function __estepi() { tooenc "$(__edate)${__tlevel}.I $(__indent "$1")"; } function __estep_() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1")"; } function __estepe_() { tooenc_ "$(__edate)${__tlevel}.E $(__indent "$1")"; } function __estepw_() { tooenc_ "$(__edate)${__tlevel}.W $(__indent "$1")"; } function __estepn_() { tooenc_ "$(__edate)${__tlevel}.N $(__indent "$1")"; } function __estepi_() { tooenc_ "$(__edate)${__tlevel}.I $(__indent "$1")"; } function __etitle() { tooenc "$(__edate)${__tlevel}=== $(__indent "$1")"; } function __ebegin() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1"): "; } function __edoto() { echo_ "."; } function __edotw() { echo_ "w"; } function __edotx() { echo_ "x"; } function __edotp() { echo_ "+"; } function __edotd() { tooenc "($1)"; } function __eendo() { echo "[ok]"; } function __eendx() { echo "[error]"; } PRETTYOPTS=() function set_verbosity() { :;} function set_interaction() { :;} function show_error() { # tester respectivement si on doit afficher les messages d'erreur, # d'avertissement, d'information, de debug return 0 } function show_warn() { return 0 } function show_info() { return 0 } function show_verbose() { return 0 } function show_debug() { [ -n "$DEBUG" ] } function check_verbosity() { return 0 } function get_verbosity_option() { :;} function check_interaction() { return 0 } function is_interaction() { return 1 } function get_interaction_option() { :;} # note: toutes les fonctions d'affichage e* écrivent sur stderr __epending= function eflush() { # Afficher les messages en attente if [ -n "$__epending" ]; then recho "$__epending" 1>&2; __epending=; fi } function eclearp() { # Supprimer les message en attente __epending= } function eerror() { # Afficher un message d'erreur show_error || return; eflush; __eerror "$*" 1>&2 } function ewarn() { # Afficher un message d'avertissement show_warn || return; eflush; __ewarn "$*" 1>&2 } function enote() { # Afficher un message d'information de même niveau qu'un avertissement show_info || return; eflush; __enote "$*" 1>&2 } function ebanner() { # Afficher un message très important encadré, puis attendre 5 secondes show_error || return; eflush; __ebanner "$*" 1>&2; sleep 5 } function eimportant() { # Afficher un message très important show_error || return; eflush; __eimportant "$*" 1>&2 } function eattention() { # Afficher un message important show_warn || return; eflush; __eattention "$*" 1>&2 } function einfo() { # Afficher un message d'information show_info || return; eflush; __einfo "$*" 1>&2 } function eecho() { # Afficher un message d'information sans préfixe show_info || return; eflush; __eecho "$*" 1>&2 } function eecho_() { show_info || return; eflush; __eecho_ "$*" 1>&2 } function edebug() { # Afficher un message de debug show_debug || return; eflush; __edebug "$*" 1>&2 } function trace() { # Afficher la commande $1..@, la lancer, puis afficher son code d'erreur si une # erreur se produit local r cmd="$(qvals "$@")" show_info && { eflush; __eecho "\$ $cmd" 1>&2; } "$@"; r=$? if [ $r -ne 0 ]; then if show_info; then eflush; __eecho "^ [EC #$r]" 1>&2 elif show_error; then eflush; __eecho "^ $cmd [EC #$r]" 1>&2; fi fi return $r } function trace_error() { # Lancer la commande $1..@, puis afficher son code d'erreur si une erreur se # produit. La différence avec trace() est que la commande n'est affichée que si # une erreur se produit. local r "$@"; r=$? if [ $r -ne 0 ]; then local cmd="$(qvals "$@")" show_error && { eflush; __eecho "^ $cmd [EC #$r]" 1>&2; } fi return $r } function etitle() { # Afficher le titre $1, qui est le début éventuel d'une section. Les section # imbriquées sont affichées indentées. La section n'est pas terminée, et il faut # la terminer explicitement avec eend, sauf dans certains cas précis: # - Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le # contexte de la section. Puis, la section est automatiquement terminée sauf si # l'option -s est spécifiée, auquel cas la section reste ouverte. Si l'option -p # est spécifiée, eclearp() est appelé pour purger les messages en attente # - Dans le cas contraire, l'option -s est ignorée: la section doit toujours # être terminée explicitement. # La fonction etitled() est comme etitle(), mais le titre n'est pas affiché # immédiatement. L'affichage effectif est effectué dès qu'une fonction e* est # utilisée. Ceci permet, avec la fonction eclearp(), de ne pas afficher de titre # pour une section vide. local __t_deferred= __t_etitle "$@" } function etitled() { local __t_deferred=1 __t_etitle "$@" } function __t_etitle() { local __t_eend=default local __t_clearp= while [ -n "$1" ]; do if [ "$1" == "--" ]; then shift break elif [ "$1" == "-s" ]; then __t_eend= shift elif [ "$1" == "--eend" ]; then __t_eend=1 shift elif [ "$1" == "-p" ]; then __t_clearp=1 shift else break fi done local __t_title="$1"; shift local __t_s=0 # etitle [ -n "$__estack" ] && __tlevel="${__tlevel} " __estack="$__estack:t" if show_info; then if [ -n "$__t_deferred" ]; then __epending="${__epending:+$__epending }$(__etitle "$__t_title")" else eflush __etitle "$__t_title" 1>&2 fi fi # commande if [ $# -gt 0 ]; then "$@" __t_s=$? [ "$__t_eend" == "default" ] && __t_eend=1 fi # eend [ "$__t_eend" == "default" ] && __t_eend= if [ -n "$__t_eend" ]; then eend $__t_s [ -n "$__t_clearp" ] && eclearp fi return $__t_s } function estep() { # Afficher la description d'une opération. Cette fonction est particulièrement # appropriée dans le contexte d'un etitle. # Les variantes e (error), w (warning), n (note), i (info) permettent d'afficher # des couleurs différentes, mais toutes sont du niveau info. show_info || return; eflush; __estep "$*" 1>&2 } function estepe() { show_info || return; eflush; __estepe "$*" 1>&2 } function estepw() { show_info || return; eflush; __estepw "$*" 1>&2 } function estepn() { show_info || return; eflush; __estepn "$*" 1>&2 } function estepi() { show_info || return; eflush; __estepi "$*" 1>&2 } function estep_() { show_info || return; eflush; __estep_ "$*" 1>&2 } function estepe_() { show_info || return; eflush; __estepe_ "$*" 1>&2 } function estepw_() { show_info || return; eflush; __estepw_ "$*" 1>&2 } function estepn_() { show_info || return; eflush; __estepn_ "$*" 1>&2 } function estepi_() { show_info || return; eflush; __estepi_ "$*" 1>&2 } function ebegin() { # Afficher le message $1, qui décrit le début d'une opération. Cette fonction # débute une section, qu'il faut terminer avec eend. # Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le # contexte de la section. Puis, la section est terminée automatiquement, sauf si # l'option -s est spécifiée, auquel cas la section reste ouverte. local __b_eend=default while [ -n "$1" ]; do if [ "$1" == "--" ]; then shift break elif [ "$1" == "-s" ]; then __b_eend= shift elif [ "$1" == "--eend" ]; then __b_eend=1 shift else break fi done local __b_msg="$1"; shift local __b_s=0 # ebegin __estack="$__estack:b" if show_info; then eflush __ebegin "$__b_msg" 1>&2 fi # commande if [ $# -gt 0 ]; then "$@" __b_s=$? [ "$__b_eend" == "default" ] && __b_eend=1 fi # eend [ "$__b_eend" == "default" ] && __b_eend= [ -n "$__b_eend" ] && eend $__b_s return $__b_s } function edot() { # Afficher une étape d'une opération, matérialisée par un point '.' ou une # croix 'x' en cas de succès ou d'erreur. Cette fonction est particulièrement # appropriée dans le contexte d'un ebegin. local s=$? show_info || return eflush [ -n "$1" ] && s="$1" shift if [ "$s" == "0" ]; then __edoto 1>&2 else __edotx 1>&2 fi show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 return $s } function edotw() { # Afficher un avertissement comme étape d'une opération, matérialisée par une # lettre 'w' (typiquement de couleur jaune). Cette fonction est particulièrement # appropriée dans le contexte d'un ebegin. local s=$? show_info || return eflush [ -n "$1" ] && s="$1" shift __edotw 1>&2 show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 return $s } function ewait() { # Afficher les étapes d'une opération qui dure, matérialisées par des '+' toutes # les secondes tant que le processus $1 tourne. # A utiliser de cette manière: # ebegin "msg" # cmd & # ewait $! # eend [ -n "$1" ] || return 1 if show_info; then local count=2 eflush little_sleep # certains processus retournent tout de suite while is_running "$1"; do sleep 1 if [ $count -gt 0 ]; then # attendre 2 secondes avant de commencer à afficher des '+' count=$(($count - 1)) else __edotp 1>&2 fi done # terminer par un '.' __edoto 1>&2 else # ne rien afficher, mais attendre quand même la fin de l'opération wait "$1" fi } function eend() { # Terminer une section. # Avec l'option -c, remettre à zéro toutes les informations de section # Si la section en cours est un ebegin, afficher la fin de l'opération: [ok] ou # [error] en fonction du code de retour de la dernière commande (ou de $1 si # cette valeur est donnée) # Si la section en cours est un etitle, marquer la fin de la section concernée # par le titre. local s=$? if [ "$1" == "-c" ]; then __estack= __tlevel= elif [ "${__estack%:b}" != "$__estack" ]; then # terminer ebegin __estack="${__estack%:b}" show_info || return eflush [ -n "$1" ] && s="$1" if [ "$s" == "0" ]; then __eendo 1>&2 else __eendx 1>&2 fi elif [ "${__estack%:t}" != "$__estack" ]; then # terminer etitle -s __estack="${__estack%:t}" __tlevel="${__tlevel% }" fi } function __elinedots() { ebegin "$1" local line if show_debug; then while read line; do __edoto 1>&2 __edotd "$line" 1>&2 done else while read line; do __edoto 1>&2 done fi eend } function elinedots() { # Afficher un message comme avec ebegin "$1", puis afficher un point '.' pour # chaque ligne lue sur stdin. Cela permet de suivre une opération. En mode # DEBUG, afficher la ligne affichée plutôt qu'un point. # Si $2..$* sont spécifiés, lancer la commande et suivre sa sortie. Ainsi, # 'elinedots msg cmd args' est un raccourci pour 'cmd args | elinedots msg' local msg="$1"; shift if [ $# -gt 0 ]; then "$@" | __elinedots "$msg" else __elinedots "$msg" fi } function ask_yesno() { # Afficher le message $1 suivi de [oN] ou [On] suivant que $2 vaut O ou N, puis # lire la réponse. Retourner 0 si la réponse est vrai, 1 sinon. # Si $1 est une option, elle est utilisée avec check_interaction pour savoir si # on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées # ($2=message, $3=default) # Si $2 vaut C, la valeur par défaut est N si on est interactif, O sinon # Si $2 vaut X, la valeur par défaut est O si on est interactif, N sinon local interactive=1 if [[ "$1" == -* ]]; then if [ "$1" != -- ]; then check_interaction "$1" || interactive= fi shift else check_interaction -c || interactive= fi local default="${2:-N}" if [ "$default" == "C" ]; then [ -n "$interactive" ] && default=N || default=O elif [ "$default" == "X" ]; then [ -n "$interactive" ] && default=O || default=N fi if [ -n "$interactive" ]; then eflush local message="$1" local prompt="[oN]" local r is_yes "$default" && prompt="[On]" if [ -n "$message" ]; then __eecho_ "$message" 1>&2 else OENC="$UTF8" __eecho_ "Voulez-vous continuer?" 1>&2 fi OENC="$UTF8" tooenc_ " $prompt " 1>&2 uread r is_yes "${r:-$default}" else is_yes "$default" fi } function ask_any() { # Afficher le message $1 suivi du texte "[$2]" (qui vaut par défaut +Oq), puis # lire la réponse. Les lettres de la chaine de format $2 sont numérotées de 0 à # $((${#2} - 1)). Le code de retour est le numéro de la lettre qui a été # sélectionnée. Cette fonction est une généralisation de ask_yesno() pour # n'importe quel ensemble de lettres. # La première lettre en majuscule est la lettre sélectionnée par défaut. # La lettre O matche toutes les lettres qui signifient oui: o, y, 1, v, t # La lettre N matche toutes les lettres qui signifient non: n, f, 0 # Il y a des raccourcis: # +O --> On # +N --> oN # +C --> oN si on est en mode interactif, On sinon # +X --> On si on est en mode interactifn oN sinon # Si $1 est une option, elle est utilisée avec check_interaction pour savoir si # on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées # ($2=message, $3=format) local interactive=1 if [[ "$1" == -* ]]; then if [ "$1" != -- ]; then check_interaction "$1" || interactive= fi shift else check_interaction -c || interactive= fi local format="${2:-+Oq}" format="${format/+O/On}" format="${format/+N/oN}" if [ -n "$interactive" ]; then format="${format/+C/oN}" format="${format/+X/On}" else format="${format/+C/On}" format="${format/+X/oN}" fi local i count="${#format}" if [ -n "$interactive" ]; then eflush local message="${1:-Voulez-vous continuer?}" local prompt="[$format]" local r f lf defi while true; do __eecho_ "$message $prompt " 1>&2 uread r r="$(strlower "${r:0:1}")" i=0; defi= while [ $i -lt $count ]; do f="${format:$i:1}" lf="$(strlower "$f")" [ "$r" == "$lf" ] && return $i if [ -z "$defi" ]; then [[ "$f" =~ [A-Z] ]] && defi="$i" fi if [ "$lf" == o ]; then case "$r" in o|y|1|v|t) return $i;; esac elif [ "$lf" == n ]; then case "$r" in n|f|0) return $i;; esac fi i=$(($i + 1)) done [ -z "$r" ] && return ${defi:-0} done else i=0 while [ $i -lt $count ]; do [[ "${format:$i:1}" =~ [A-Z] ]] && return $i i=$(($i + 1)) done return 0 fi } function read_value() { # Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non # vide, puis lire la valeur donnée par l'utilisateur. Cette valeur doit être non # vide si $4(=O) est vrai. La valeur saisie est placée dans la variable # $2(=value) # Si $1 est une option, elle est utilisée avec check_interaction pour savoir si # on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées # ($2=message, $3=variable, $4=default, $5=required) # En mode non interactif, c'est la valeur par défaut qui est sélectionnée. Si # l'utilisateur requière que la valeur soit non vide et que la valeur par défaut # est vide, afficher un message d'erreur et retourner faux # read_password() est comme read_value(), mais la valeur saisie n'est pas # affichée, ce qui la rend appropriée pour la lecture d'un mot de passe. local -a __rv_opts __rv_readline=1 __rv_showdef=1 __rv_nl= __rv_opts=() [ -n "$UTOOLS_NO_READLINE" ] && __rv_readline= __rv_read "$@" } function read_password() { local -a __rv_opts __rv_readline= __rv_showdef= __rv_nl=1 __rv_opts=(-s) __rv_read "$@" } function __rv_read() { local __rv_int=1 if [[ "$1" == -* ]]; then if [ "$1" != -- ]; then check_interaction "$1" || __rv_int= fi shift else check_interaction -c || __rv_int= fi local __rv_msg="$1" __rv_v="${2:-value}" __rv_d="$3" __rv_re="${4:-O}" if [ -z "$__rv_int" ]; then # En mode non interactif, retourner la valeur par défaut if is_yes "$__rv_re" && [ -z "$__rv_d" ]; then OENC="$UTF8" eerror "La valeur par défaut de $__rv_v doit être non vide" return 1 fi _setv "$__rv_v" "$__rv_d" return 0 fi eflush local __rv_r while true; do if [ -n "$__rv_msg" ]; then __eecho_ "$__rv_msg" 1>&2 else OENC="$UTF8" __eecho_ "Entrez la valeur" 1>&2 fi if [ -n "$__rv_readline" ]; then OENC="$UTF8" tooenc_ ": " 1>&2 uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r else if [ -n "$__rv_d" ]; then if [ -n "$__rv_showdef" ]; then tooenc_ " [$__rv_d]" 1>&2 else tooenc_ " [****]" 1>&2 fi fi OENC="$UTF8" tooenc_ ": " 1>&2 uread "${__rv_opts[@]}" __rv_r [ -n "$__rv_nl" ] && echo fi __rv_r="${__rv_r:-$__rv_d}" if [ -n "$__rv_r" ] || ! is_yes "$__rv_re"; then _setv "$__rv_v" "$__rv_r" return 0 fi done } function simple_menu() { # Afficher un menu simple dont les éléments sont les valeurs du tableau # $2(=options). L'option choisie est placée dans la variable $1(=option) # -t TITLE: spécifier le titre du menu # -m YOUR_CHOICE: spécifier le message d'invite pour la sélection de l'option # -d DEFAULT: spécifier l'option par défaut. Par défaut, prendre la valeur # actuelle de la variable $1(=option) local __sm_title= __sm_yourchoice= __sm_default= local -a __sm_args parse_opts -t: __sm_title= -m: __sm_yourchoice= -d: __sm_default= @ __sm_args -- "$@" && set -- "${__sm_args[@]}" || ewarn "$__sm_args" local __sm_option_var="${1:-option}" __sm_options_var="${2:-options}" local __sm_option __sm_options __sm_options="$__sm_options_var[*]" if [ -z "${!__sm_options}" ]; then OENC="$UTF8" eerror "Le tableau $__sm_options_var doit être non vide" return 1 fi [ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}" eflush array_copy __sm_options "$__sm_options_var" local __sm_c=0 __sm_i __sm_choice while true; do if [ "$__sm_c" == "0" ]; then # Afficher le menu [ -n "$__sm_title" ] && __eecho "=== $__sm_title ===" 1>&2 __sm_i=1 for __sm_option in "${__sm_options[@]}"; do if [ "$__sm_option" == "$__sm_default" ]; then __eecho "$__sm_i*- $__sm_option" 1>&2 else __eecho "$__sm_i - $__sm_option" 1>&2 fi let __sm_i=$__sm_i+1 done fi # Afficher les choix if [ -n "$__sm_yourchoice" ]; then __eecho_ "$__sm_yourchoice" 1>&2 else OENC="$UTF8" __eecho_ "Entrez le numéro de l'option choisie" 1>&2 fi OENC="$UTF8" tooenc_ ": " 1>&2 uread __sm_choice # Valeur par défaut if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then __sm_option="$__sm_default" break fi # Vérifier la saisie if [ -n "$__sm_choice" -a -z "${__sm_choice//[0-9]/}" ]; then if [ "$__sm_choice" -gt 0 -a "$__sm_choice" -le "${#__sm_options[*]}" ]; then __sm_option="${__sm_options[$(($__sm_choice - 1))]}" break else OENC="$UTF8" eerror "Numéro d'option incorrect" fi else OENC="$UTF8" eerror "Vous devez saisir le numéro de l'option choisie" fi let __sm_c=$__sm_c+1 if [ "$__sm_c" -eq 5 ]; then # sauter une ligne toutes les 4 tentatives OENC="$UTF8" tooenc "" 1>&2 __sm_c=0 fi done _setv "$__sm_option_var" "$__sm_option" } function actions_menu() { # Afficher un menu dont les éléments sont les valeurs du tableau $4(=options), # et une liste d'actions tirées du tableau $3(=actions). L'option choisie est # placée dans la variable $2(=option). L'action choisie est placée dans la # variable $1(=action) # Un choix est saisi sous la forme [action]num_option # -t TITLE: spécifier le titre du menu # -m OPT_YOUR_CHOICE: spécifier le message d'invite pour la sélection de # l'action et de l'option # -M ACT_YOUR_CHOICE: spécifier le message d'invite dans le cas où aucune option # n'est disponible. Dans ce cas, seules les actions vides sont possibles. # -e VOID_ACTION: spécifier qu'une action est vide, c'est à dire qu'elle ne # requière pas d'être associée à une option. Par défaut, la dernière action # est classée dans cette catégorie puisque c'est l'action "quitter" # -d DEFAULT_ACTION: choisir l'action par défaut. par défaut, c'est la première # action. # -q QUIT_ACTION: choisir l'option "quitter" qui provoque la sortie du menu sans # choix. par défaut, c'est la dernière action. # -o DEFAULT_OPTION: choisir l'option par défaut. par défaut, prendre la valeur # actuelle de la variable $2(=option) local -a __am_action_descs __am_options __am_void_actions local __am_tmp __am_select_action __am_select_option __am_title __am_optyc __am_actyc local __am_default_action=auto __am_quit_action=auto local __am_default_option= local -a __am_args parse_opts \ -t: __am_title= \ -m: __am_optyc= \ -M: __am_actyc= \ -e: __am_void_actions \ -d: __am_default_action= \ -q: __am_quit_action= \ -o: __am_default_option= \ @ __am_args -- "$@" && set -- "${__am_args[@]}" || { eerror "$__am_args"; return 1; } __am_tmp="${1:-action}"; __am_select_action="${!__am_tmp}" __am_tmp="${2:-option}"; __am_select_option="${!__am_tmp}" [ -n "$__am_default_option" ] && __am_select_option="$__am_default_option" array_copy __am_action_descs "${3:-actions}" array_copy __am_options "${4:-options}" eerror_unless [ ${#__am_action_descs[*]} -gt 0 ] "Vous devez spécifier le tableau des actions" || return __actions_menu || return 1 _setv "${1:-action}" "$__am_select_action" _setv "${2:-option}" "$__am_select_option" } function __actions_menu() { local title="$__am_title" local optyc="$__am_optyc" actyc="$__am_actyc" local default_action="$__am_default_action" local quit_action="$__am_quit_action" local select_action="$__am_select_action" local select_option="$__am_select_option" local -a action_descs options void_actions array_copy action_descs __am_action_descs array_copy options __am_options array_copy void_actions __am_void_actions # Calculer la liste des actions valides local no_options array_isempty options && no_options=1 local -a actions local tmp action name for tmp in "${action_descs[@]}"; do splitfsep2 "$tmp" : action name [ -n "$action" ] || action="${name:0:1}" action="$(strlower "$action")" array_addu actions "$action" done # Calculer l'action par défaut if [ "$default_action" == auto ]; then # si action par défaut non spécifiée, alors prendre la première action default_action="$select_action" if [ -n "$default_action" ]; then array_contains actions "$default_action" || default_action= fi [ -n "$default_action" ] || default_action="${actions[0]}" fi default_action="${default_action:0:1}" default_action="$(strlower "$default_action")" # Calculer l'action quitter par défaut if [ "$quit_action" == auto ]; then # si action par défaut non spécifiée, alors prendre la dernière action, # s'il y a au moins 2 actions if [ ${#actions[*]} -gt 1 ]; then quit_action="${actions[@]:$((-1)):1}" array_addu void_actions "$quit_action" fi fi quit_action="${quit_action:0:1}" quit_action="$(strlower "$quit_action")" # Calculer la ligne des actions à afficher local action_title for tmp in "${action_descs[@]}"; do splitfsep2 "$tmp" : action name [ -n "$action" ] || action="${name:0:1}" [ -n "$name" ] || name="$action" action="$(strlower "$action")" if [ -n "$no_options" ]; then if ! array_contains void_actions "$action"; then array_del actions "$action" continue fi fi [ "$action" == "$default_action" ] && name="$name*" action_title="${action_title:+$action_title/}$name" done if [ -n "$default_action" ]; then # si action par défaut invalide, alors pas d'action par défaut array_contains actions "$default_action" || default_action= fi if [ -n "$quit_action" ]; then # si action quitter invalide, alors pas d'action quitter array_contains actions "$quit_action" || quit_action= fi # Type de menu if [ -n "$no_options" ]; then if array_isempty void_actions; then eerror "Aucune option n'est définie. Il faut définir le tableau des actions vides" return 1 fi __void_actions_menu else __options_actions_menu fi } function __void_actions_menu() { eflush local c=0 choice while true; do if [ $c -eq 0 ]; then [ -n "$title" ] && __etitle "$title" 1>&2 __eecho_ "=== Actions disponibles: " 1>&2 tooenc "$action_title" 1>&2 fi if [ -n "$actyc" ]; then __eecho_ "$actyc" 1>&2 elif [ -n "$optyc" ]; then __eecho_ "$optyc" 1>&2 else __eecho_ "Entrez l'action à effectuer" 1>&2 fi tooenc_ ": " 1>&2 uread choice if [ -z "$choice" -a -n "$default_action" ]; then select_action="$default_action" break fi # vérifier la saisie choice="${choice:0:1}" choice="$(strlower "$choice")" if array_contains actions "$choice"; then select_action="$choice" break elif [ -n "$choice" ]; then eerror "$choice: action incorrecte" else eerror "vous devez saisir l'action à effectuer" fi let c=$c+1 if [ $c -eq 5 ]; then # sauter une ligne toutes les 4 tentatives tooenc "" 1>&2 c=0 fi done __am_select_action="$select_action" __am_select_option= } function __options_actions_menu() { eflush local c=0 option choice action option while true; do if [ $c -eq 0 ]; then [ -n "$title" ] && __etitle "$title" 1>&2 i=1 for option in "${options[@]}"; do if [ "$option" == "$select_option" ]; then tooenc "$i*- $option" 1>&2 else tooenc "$i - $option" 1>&2 fi let i=$i+1 done __estepn_ "Actions disponibles: " 1>&2 tooenc "$action_title" 1>&2 fi if [ -n "$optyc" ]; then __eecho_ "$optyc" 1>&2 else __eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2 fi tooenc_ ": " 1>&2 uread choice # vérifier la saisie if [ -z "$choice" -a -n "$default_action" ]; then action="$default_action" if array_contains void_actions "$action"; then select_action="$action" select_option= break elif [ -n "$select_option" ]; then select_action="$action" break fi fi action="${choice:0:1}" action="$(strlower "$action")" if array_contains actions "$action"; then # on commence par un code d'action valide. cool :-) if array_contains void_actions "$action"; then select_action="$action" select_option= break else option="${choice:1}" option="${option// /}" if [ -z "$option" -a -n "$select_option" ]; then select_action="$action" break elif [ -z "$option" ]; then eerror "vous devez saisir le numéro de l'option" elif isnum "$option"; then if [ $option -gt 0 -a $option -le ${#options[*]} ]; then select_action="$action" select_option="${options[$(($option - 1))]}" break fi else eerror "$option: numéro d'option incorrecte" fi fi elif isnum "$choice"; then # on a simplement donné un numéro d'option action="$default_action" if [ -n "$action" ]; then if array_contains void_actions "$action"; then select_action="$action" select_option= break else option="${choice// /}" if [ -z "$option" ]; then eerror "vous devez saisir le numéro de l'option" elif isnum "$option"; then if [ $option -gt 0 -a $option -le ${#options[*]} ]; then select_action="$action" select_option="${options[$(($option - 1))]}" break fi else eerror "$option: numéro d'option incorrecte" fi fi else eerror "Vous devez spécifier l'action à effectuer" fi elif [ -n "$choice" ]; then eerror "$choice: action et/ou option incorrecte" else eerror "vous devez saisir l'action à effectuer" fi let c=$c+1 if [ $c -eq 5 ]; then # sauter une ligne toutes les 4 tentatives tooenc "" 1>&2 c=0 fi done __am_select_action="$select_action" __am_select_option="$select_option" } ################################################################################ ## fichiers temporaires # autoclean: gérer une liste de fichiers temporaires à supprimer en fin de # programme function __ac_forgetall() { __ac_files=(); } __ac_forgetall function __ac_trap() { local file for file in "${__ac_files[@]}"; do [ -e "$file" ] && rm -rf "$file" 2>/dev/null done __ac_forgetall } trap __ac_trap 1 3 15 EXIT function autoclean() { # Ajouter $1..$n à la liste des fichiers à supprimer à la fin du programme local file for file in "$@"; do [ -n "$file" ] && array_add __ac_files "$file" done } function ac_cleanall() { # Supprimer *tous* les fichiers temporaires gérés par autoclean tout de suite. __ac_trap } function ac_clean() { # Supprimer les fichier temporaires $1..$* si et seulement s'ils ont été générés # par ac_set_tmpfile ou ac_set_tmpdir local file for file in "$@"; do if array_contains __ac_files "$file"; then [ -e "$file" ] && rm -rf "$file" 2>/dev/null array_del __ac_files "$file" fi done } function ac_set_tmpfile() { # Créer un fichier temporaire avec le motif $2, l'ajouter à la liste des # fichiers à supprimer en fin de programme, et mettre sa valeur dans la # variable $1 # En mode debug, si ($5 est vide ou ${!5} est une valeur vraie), et si $3 n'est # pas vide, prendre ce fichier au lieu de générer un nouveau fichier # temporaire. Si $4==keep, ne pas écraser le fichier $3 s'il existe. local __acst_d if show_debug; then if [ -n "$5" ]; then is_yes "${!5}" && __acst_d=1 else __acst_d=1 fi fi if [ -n "$__acst_d" -a -n "$3" ]; then _setv "$1" "$3" [ -f "$3" -a "$4" == keep ] || >"$3" else local __acst_t="$(mktempf "$2")" autoclean "$__acst_t" _setv "$1" "$__acst_t" fi } function ac_set_tmpdir() { # Créer un répertoire temporaire avec le motif $2, l'ajouter à la liste des # fichiers à supprimer en fin de programme, et mettre sa valeur dans la # variable $1 # En mode debug, si ($4 est vide ou ${!4} est une valeur vraie), et si $3 n'est # pas vide, prendre ce nom de répertoire au lieu de créer un nouveau répertoire # temporaire local __acst_d if show_debug; then if [ -n "$4" ]; then is_yes "${!4}" && __acst_d=1 else __acst_d=1 fi fi if [ -n "$__acst_d" -a -n "$3" ]; then _setv "$1" "$3" mkdir -p "$3" else local __acst_t="$(mktempd "$2")" autoclean "$__acst_t" _setv "$1" "$__acst_t" fi } function debug_tee() { # En mode debug, passer le flux à travers la commande 'tee "$@"'. Sinon, le flux # est passé inchangé. if show_debug; then tee "$@" else cat fi } ################################################################################ ## environnement function get_user_defaults_file() { # Afficher le chemin vers le fichier utilisateur à éditer pour qu'il soit chargé # par 'set_defaults $1'. Ce fichier n'existe pas forcément; il faut peut-être le # créer. if [ -r "$HOME/etc/default.${HOSTNAME%%.*}/$1" ]; then echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" elif [ -r "$HOME/etc/default/$1" ]; then echo "$HOME/etc/default/$1" elif [ -n "$UTOOLS_LOCAL_PROFILES" ]; then echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" else echo "$HOME/etc/default/$1" fi } function get_defaults_files() { # Initialiser le tableau $1(=defaults) avec la liste des fichiers qui seraient # chargés par la commande 'set_defaults $2..N' local __gd_dest="${1:-defaults}"; shift local -a __gd_fs local __gd_f __gd_found for __gd_f in "$@"; do __gd_found= if [ -r "/etc/default/$__gd_f" ]; then __gd_fs=("${__gd_fs[@]}" "/etc/default/$__gd_f") __gd_found=1 fi if [ -r "$HOME/etc/default.${HOSTNAME%%.*}/$__gd_f" ]; then __gd_fs=("${__gd_fs[@]}" "$HOME/etc/default.${HOSTNAME%%.*}/$__gd_f") __gd_found=1 elif [ -r "$HOME/etc/default/$__gd_f" ]; then __gd_fs=("${__gd_fs[@]}" "$HOME/etc/default/$__gd_f") __gd_found=1 fi if [ -z "$__gd_found" -a -r "$scriptdir/lib/default/$__gd_f" ]; then # si pas de fichier dans /etc/default ni dans ~/etc/default, # utiliser $scriptdir/lib/default/$arg en dernier lieu __gd_fs=("${__gd_fs[@]}" "$scriptdir/lib/default/$__gd_f") fi done array_copy "$__gd_dest" __gd_fs } function set_defaults() { # Pour chaque argument, sourcer /etc/default/$arg *et* (en priorité # ~/etc/default.$HOSTNAME/$arg ou à défaut ~/etc/default/$arg) si ceux-ci # existent. *Sinon*, lire $scriptdir/lib/default/$arg si ce fichier existe local -a __sd_fs local __sd_f get_defaults_files __sd_fs "$@" for __sd_f in "${__sd_fs[@]}"; do source "$__sd_f" done } ################################################################################ ## Informations sur le système local # Nom d'hôte respectivement avec et sans domaine # contrairement à $HOSTNAME, cette valeur peut être spécifiée, comme par ruinst : "${MYHOST:=$HOSTNAME}" : "${MYHOSTNAME:=${MYHOST%%.*}}" export MYHOST MYHOSTNAME function myhost() { # Afficher le nom d'hôte pleinement qualifié, en faisant appel à la commande # hostname. Par comparaison, $MYHOST est fourni par bash. hostname -f 2>/dev/null || echo "$MYHOST" } function myhostname() { # Afficher le nom d'hôte sans domaine, en faisant appel à la commande # hostname. Par comparaison, $MYHOSTNAME est fourni par bash. hostname -s 2>/dev/null || echo "$MYHOSTNAME" }