Comment renommer une fonction bash?
Je développe des wrappers de commodité autour d'un autre logiciel qui définit une fonction bash. Je voudrais remplacer leur fonction bash par une fonction identique, tout en étant capable d'exécuter leur fonction à partir de la mienne. En d'autres termes, je dois soit renommer leur fonction, soit créer une sorte d'alias persistant qui ne sera pas modifié lorsque je crée ma fonction du même nom.
Pour donner un bref exemple d'une tentative naïve que je Je ne m'attendais pas à travailler (et en effet ce n'est pas le cas):
$ theirfunc() { echo "do their thing"; }
$ _orig_theirfunc() { theirfunc; }
$ theirfunc() { echo "do my thing"; _orig_theirfunc }
$ theirfunc
do my thing
do my thing
do my thing
...
Évidemment, je ne veux pas de récursivité infinie, je veux:
do my thing
do their thing
Comment puis-je faire cela?
8 réponses
Voici un moyen d'éliminer le fichier temporaire:
$ theirfunc() { echo "do their thing"; }
$ eval "$(echo "orig_theirfunc()"; declare -f theirfunc | tail -n +2)"
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing
Aha. Trouvé une solution, bien que ce ne soit pas très joli:
$ theirfunc() { echo "do their thing"; }
$ echo "orig_theirfunc()" > tmpfile
$ declare -f theirfunc | tail -n +2 >> tmpfile
$ source tmpfile
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing
Je suis sûr que cela pourrait être amélioré par un véritable assistant bash. En particulier, ce serait bien d'abandonner le besoin d'un tempfile.
Update : Evan Broder, assistant de bash, a relevé le défi (voir la réponse acceptée ci-dessus). J'ai reformulé sa réponse en une fonction générique "copy_function":
# copies function named $1 to name $2
copy_function() {
declare -F $1 > /dev/null || return 1
eval "$(echo "${2}()"; declare -f ${1} | tail -n +2)"
}
Peut être utilisé comme ceci:
$ theirfunc() { echo "do their thing"; }
$ copy_function theirfunc orig_theirfunc
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing
Très joli!
Plus golfed les copy_function
et rename_function
fonctions à:
copy_function() {
test -n "$(declare -f $1)" || return
eval "${_/$1/$2}"
}
rename_function() {
copy_function $@ || return
unset -f $1
}
À partir de la solution de @ Dmitri Rubinstein:
- Pas besoin d'appeler
declare
deux fois. La vérification des erreurs fonctionne toujours. - éliminez temp var (
func
) en utilisant la variable spéciale_
.- Note: l'utilisation de
test -n ...
était la seule façon de préserver_
et de pouvoir toujours revenir en cas d'erreur.
- Note: l'utilisation de
- Modifier
return 1
àreturn
(qui renvoie l'état actuel code) - utilisez une substitution de motif plutôt que la suppression de préfixe.
Une fois copy_function
défini, cela rend rename_function
trivial. (Il suffit de ne pas renommer copy_function
;-)
Si vous voulez juste ajouter quelque chose au nom, dites orig_
, Alors je pense que le plus simple est
eval orig_"$(declare -f theirfun)"
La fonction copy_function peut être améliorée en utilisant l'extension du paramètre shell au lieu de la commande tail:
copy_function() {
declare -F "$1" > /dev/null || return 1
local func="$(declare -f "$1")"
eval "${2}(${func#*\(}"
}
Pour résumer toutes les autres solutions et les corriger partiellement, voici la solution qui:
- n'utilise pas
declare
deux fois - n'a pas besoin de programmes externes (comme
tail
) - ne fait aucun remplacement inattendu
- est relativement court
- vous protège contre les bugs de programmation habituels grâce à des citations correctes
Mais:
- Il ne fonctionne probablement pas sur les fonctions récursives, comme le nom de la fonction utilisée pour la récursivité dans la copie n'est pas remplacé. Obtenir un tel remplacement est une tâche beaucoup trop complexe. Si vous voulez utiliser de tels remplacements, vous pouvez essayer cette réponse https://stackoverflow.com/a/18839557 avec
eval "${_//$1/$2}"
au lieu deeval "${_/$1/$2}"
(Notez le double//
). Cependant, le remplacement du nom échoue sur des noms de fonctions trop simples (commea
) et échoue pour la récursivité calculée (commecommand_help() { case "$1" in ''|-help) echo "help [command]"; return;; esac; "command_$1" -help "${@:2}"; }
)
Tout combiné:
: rename_fn oldname newname
rename_fn()
{
local a
a="$(declare -f "$1")" &&
eval "function $2 ${a#*"()"}" &&
unset -f "$1";
}
Maintenant les tests:
somefn() { echo one; }
rename_fn somefn thatfn
somefn() { echo two; }
somefn
thatfn
Sorties comme requis:
two
one
Essayez maintenant des cas plus compliqués, qui donnent tous les résultats attendus ou échouent:
rename_fn unknown "a b"; echo $?
rename_fn "a b" murx; echo $?
a(){ echo HW; }; rename_fn " a " b; echo $?; a
a(){ echo "'HW'"; }; rename_fn a b; echo $?; b
a(){ echo '"HW"'; }; rename_fn a b; echo $?; b
a(){ echo '"HW"'; }; rename_fn a "b c"; echo $?; a
On peut soutenir que suivre est toujours un bug:
a(){ echo HW; }; rename_fn a " b "; echo $?; b
Comme il devrait échouer car " b "
n'est pas un nom de fonction correct. Si vous voulez vraiment cela, vous avez besoin de la variante suivante:
rename_fn()
{
local a
a="$(declare -f "$1")" &&
eval "function $(printf %q "$2") ${a#*"()"}" &&
unset -f "$1";
}
Maintenant, cela attrape ce cas artificiel, aussi. (Veuillez noter que printf
avec %q
est un bash
intégré.)
Bien sûr, vous pouvez diviser cela en copier + renommer comme ceci:
copy_fn() { local a; a="$(declare -f "$1")" && eval "function $(printf %q "$2") ${a#*"()"}"; }
rename_fn() { copy_fn "$@" && unset -f "$1"; }
J'espère que c'est la solution à 101%. Si elle a besoin d'amélioration, veuillez commenter ;)
Voici une fonction basée sur l'approche de @ Evan Broder:
# Syntax: rename_function <old_name> <new_name>
function rename_function()
{
local old_name=$1
local new_name=$2
eval "$(echo "${new_name}()"; declare -f ${old_name} | tail -n +2)"
unset -f ${old_name}
}
Une fois cela défini, vous pouvez simplement faire rename_function func orig_func
Notez que vous pouvez utiliser une approche connexe pour décorer / modifier / envelopper les fonctions existantes, comme dans la réponse DE @phs:
# Syntax: prepend_to_function <name> [statements...]
function prepend_to_function()
{
local name=$1
shift
local body="$@"
eval "$(echo "${name}(){"; echo ${body}; declare -f ${name} | tail -n +3)"
}
# Syntax: append_to_function <name> [statements...]
function append_to_function()
{
local name=$1
shift
local body="$@"
eval "$(declare -f ${name} | head -n -1; echo ${body}; echo '}')"
}
Une fois ceux-ci définis, disons que vous avez une fonction existante comme suit:
function foo()
{
echo stuff
}
, Alors vous pouvez faire:
prepend_to_function foo echo before
append_to_function foo echo after
Utiliser declare -f foo
, nous pouvons voir l'effet:
foo ()
{
echo before;
echo stuff;
echo after
}
Pour ceux d'entre nous obligés d'être compatibles avec bash 3.2 (vous savez de qui nous parlons), declare -f
ne fonctionne pas. J'ai trouvé type
peut fonctionner
eval "$(type my_func | sed $'1d;2c\\\nmy_func_copy()\n')"
Sous forme de fonction, cela ressemblerait à
copy_function()
{
eval "$(type "${1}"| sed $'1d;2c\\\n'"${2}"$'()\n')"
}
Et si vous voulez vraiment ne pas compter sur sed
...
function copy_function()
{
eval "$({
IFS='' read -r line
IFS='' read -r line
echo "${2} ()"
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
done
}< <(type "${1}"))"
}
Mais c'est un peu verbeux pour moi