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?

26
demandé sur Carl Meyer 2009-07-30 03:07:53

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
37
répondu Evan Broder 2009-09-02 17:57:12

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!

13
répondu Carl Meyer 2009-09-02 18:21:57

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.
  • 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;-)

11
répondu ingydotnet 2013-09-17 00:41:11

Si vous voulez juste ajouter quelque chose au nom, dites orig_, Alors je pense que le plus simple est

eval orig_"$(declare -f theirfun)"
6
répondu parched 2015-09-06 16:41:09

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#*\(}"
}
5
répondu Dmitri Rubinstein 2012-07-27 09:30:05

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 de eval "${_/$1/$2}" (Notez le double //). Cependant, le remplacement du nom échoue sur des noms de fonctions trop simples (comme a) et échoue pour la récursivité calculée (comme command_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 ;)

3
répondu Tino 2017-05-23 12:32:17

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
}
2
répondu Gareth Stockwell 2012-05-21 09:12:13

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

1
répondu Andy 2018-02-28 16:23:33