nutools/lib/ulib/base

4159 lines
133 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##@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
s/a\xCC\x80/à/g
s/e\xCC\x81/é/g; s/e\xCC\x80/è/g; s/e\xCC\x82/ê/g; s/e\xCC\x88/ë/g
s/i\xCC\x88/ï/g; s/i\xCC\x82/î/g
s/o\xCC\x82/ô/g; s/o\xCC\x88/ö/g
s/u\xCC\x88/ü/g; s/u\xCC\x82/û/g
s/c\xCC\xA7/ç/g
s/A\xCC\x80/À/g
s/E\xCC\x81/É/g; s/E\xCC\x80/È/g; s/E\xCC\x82/Ê/g; s/E\xCC\x88/Ë/g
s/I\xCC\x88/Ï/g; s/I\xCC\x82/Î/g
s/O\xCC\x82/Ô/g; s/O\xCC\x88/Ö/g
s/U\xCC\x88/Ü/g; s/U\xCC\x82/Û/g
s/C\xCC\xA7/Ç/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() {
</etc/passwd awkrun FS=: '($3 + 0) >= 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(/&/, "\\&amp;", s)
gsub(/"/, "\\&quot;", s)
gsub(/>/, "\\&gt;", s)
gsub(/</, "\\&lt;", s)
return s
}
function unquote_html(s) {
gsub(/&lt;/, "<", s)
gsub(/&gt;/, ">", s)
gsub(/&quot;/, "\"", s)
gsub(/&amp;/, "\\&", 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))
} else if (match(date, /([0-9][0-9]?)\/([0-9][0-9]?)\/([0-9][0-9])/, parts)) {
basey = int(strftime("%Y")); basey = basey - basey % 100
y = basey + 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 <input) > 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 <file
} else {
getline
}
return parsecsv($0)
}
function __csv_should_quote(s) {
if (s ~ /^[[:blank:][:cntrl:][:space:]]/) return 1
if (s ~ /[[:blank:][:cntrl:][:space:]]$/) return 1
return 0
}
function array_formatcsv2(fields, colsep, mvsep, qchar, echar, count, indices, line, i, value) {
line = ""
count = mkindices(fields, indices)
for (i = 1; i <= count; i++) {
value = fields[indices[i]]
if (i > 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 <input) > 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 -XFR "$@"
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
[ -z "${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
f="${format:$i:1}"
[ -z "${f/[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"
}