2009-07-29 7 views
25

Estoy desarrollando algunas envolturas de conveniencia alrededor de otro paquete de software que define una función bash. Me gustaría reemplazar su función bash con una función propia con un nombre idéntico, sin dejar de poder ejecutar su función desde dentro de la mía. En otras palabras, necesito cambiar el nombre de su función o crear algún tipo de alias persistente que no se modifique cuando creo mi función del mismo nombre.¿Cómo cambio el nombre de una función bash?

para dar un breve ejemplo de un intento ingenuo que no esperaba a trabajar (y de hecho no lo hace):

$ 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 
... 

Obviamente no quiero recursividad infinita, quiero:

do my thing 
do their thing 

¿Cómo puedo hacer esto?

Respuesta

37

Aquí está una manera de eliminar el archivo temporal:

$ 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 
+2

Brillante! Gracias, bash wizard ;-) –

+0

Parece que te perdiste ** {** en la línea 2: 'eval" $ (echo "orig_theirfunc() {"; declare -f theirfunc | tail -n +2) ''. O tal vez, en algunas configuraciones, declare que las impresiones abren una llave en una nueva línea. En ese caso, el script será más complicado. – Poma

+2

Otra posibilidad que también trataría con funciones recursivas: 'eval '$ (declare -f theirfunc | sed'// btheirfunc \ b/orig_theirfunc/g)" ' –

13

Aha. Encontré una solución, aunque no es muy bonita:

$ 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 

Estoy seguro de que esto podría ser mejorado por un verdadero asistente de bash. En particular, sería bueno descartar la necesidad de un archivo temporal.

Actualización: asistente de bash Evan Broder se levantó al desafío (ver respuesta aceptada arriba). Me reformuló su respuesta en una función genérica "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)" 
} 

Puede ser utilizado de esta manera:

$ theirfunc() { echo "do their thing"; } 
$ copy_function theirfunc orig_theirfunc 
$ theirfunc() { echo "do my thing"; orig_theirfunc; } 
$ theirfunc 
do my thing 
do their thing 

muy agradable!

+7

Bash inheritance! Nunca pensé que vería el día. – shuckster

+0

@carlmeyer gracias! amo la actualización en una función reutilizable – Integralist

+0

Al poner un comentario a la respuesta de Evan, otra posibilidad que también se ocuparía de las funciones recursivas: eval "$ (declare -f theirfunc | sed 's/\ btheirfunc \ b/orig_theirfunc/g) " –

2

Aquí es una función basada en el enfoque 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} 
} 

Una vez que esto se define, puede simplemente hacer rename_function func orig_func

Tenga en cuenta que puede usar un enfoque relacionado para decorar/modificar/ajustar funciones existentes, como en la respuesta de @ phs:

# Syntax: prepend_to_function <name> [statements...] 
function prepend_to_function() 
{ 
    local name=$1 
    shift 
    local body="[email protected]" 
    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="[email protected]" 
    eval "$(declare -f ${name} | head -n -1; echo ${body}; echo '}')" 
} 

Una vez que estos se definen, digamos que usted tiene una función existente de la siguiente manera:

function foo() 
{ 
    echo stuff 
} 

A continuación, puede hacer:

prepend_to_function foo echo before 
append_to_function foo echo after 

Usando declare -f foo, se puede ver el efecto:

foo() 
{ 
    echo before; 
    echo stuff; 
    echo after 
} 
5

La función copy_function se puede mejorar utilizando el parámetro de shell e Xpansion en lugar de cola de comandos:

copy_function() { 
    declare -F "$1" > /dev/null || return 1 
    local func="$(declare -f "$1")" 
    eval "${2}(${func#*\(}" 
} 
11

golfed Además los copy_function y rename_function funciones para:

copy_function() { 
    test -n "$(declare -f $1)" || return 
    eval "${_/$1/$2}" 
} 

rename_function() { 
    copy_function [email protected] || return 
    unset -f $1 
} 

A partir de la solución de @Dmitri Rubinstein:

  • No hay necesidad de llamar a declare dos veces. La comprobación de errores todavía funciona.
  • Elimine temp var (func) utilizando la variable especial _.
    • Nota: el uso de test -n ... fue la única forma en que se me ocurrió crear _ y aún así poder devolver el error.
  • Cambio return 1 a return (que devuelve el código de estado actual)
  • Utilice un patrón de sustitución en lugar de la eliminación de prefijo.

Una vez que copy_function se ha definido, hace rename_function trivial.(Pero no cambiar el nombre de copy_function ;-)

+0

Para copiar, sería mucho más simple decir 'newname() {oldname" $ ​​@ ";}' Por supuesto que me doy cuenta de que no ayuda a renombrar, ya que todavía requiere 'oldname', y me doy cuenta de que el OP específicamente preguntó sobre el cambio de nombre, pero simplemente señalando que esto sería más corto en caso de que todo lo que uno quiere es copiar. Supongo que un pequeño costo de llamada de función estaría allí aunque ... – jamadagni

+0

@jamadagni 'newname() {oldname" $ ​​@ ";}' no es una copia, es un aliasing. 'a() {echo 1;}; b() {a;}; a() {echo 2;}; b' emite '2' mientras que debe salir' 1' si 'b' es una copia real – Tino

+2

' rename_function "ab" 'hace algunas cosas muy inesperadas debido a que falta la cotización. Solución sugerida:' copy_function() {test -n "$ (declarar -f $ 1)" && eval "$ {_/$ 1/$ 2}";}; rename_function() {copy_function "$ @" && unset -f "$ 1";} '(sin embargo, esto no soluciona todos posible mal uso de esto, pero la mayoría) – Tino

3

Si lo que desea es algo que anteponer el nombre, digo orig_, entonces creo que lo más sencillo es

eval orig_"$(declare -f theirfun)" 
+0

Se ve tan simple :-) – Scz

3

En resumen todas las otras soluciones y parcialmente corregirlos, aquí está la solución que:

  • no utiliza el doble declare
  • no necesita programas externos (como tail)
  • no hace reemplazos inesperados
  • es relativamente corto
  • le protege frente a errores de programación habituales gracias a una correcta citando

Pero:

  • Probablemente no funciona en las funciones recursivas, como el el nombre de función utilizado para la recursión dentro de la copia no se reemplaza. Conseguir ese derecho de reemplazo es una tarea demasiado compleja. Si desea utilizar tales reemplazos, puede probar esta respuesta https://stackoverflow.com/a/18839557 con eval "${_//$1/$2}" en lugar de eval "${_/$1/$2}" (tenga en cuenta el doble //). Sin embargo reemplazando el nombre produce un error en los nombres de funciones muy simples (como a) y falla por recursividad calculada (como command_help() { case "$1" in ''|-help) echo "help [command]"; return;; esac; "command_$1" -help "${@:2}"; })

Todo combinado:

: rename_fn oldname newname 
rename_fn() 
{ 
    local a 
    a="$(declare -f "$1")" && 
    eval "function $2 ${a#*"()"}" && 
    unset -f "$1"; 
} 

ahora las pruebas:

somefn() { echo one; } 
rename_fn somefn thatfn 
somefn() { echo two; } 
somefn 
thatfn 

salidas según sea necesario:

two 
one 

Ahora probar algunos casos más complicados, que todos dan los resultados esperados o se abstiene:

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 

Se puede argumentar que sigue es todavía un error:

a(){ echo HW; }; rename_fn a " b "; echo $?; b 

como debe fallar como " b " no está un nombre de función correcto. Si realmente quiere esto, necesita la siguiente variante:

rename_fn() 
{ 
    local a 
    a="$(declare -f "$1")" && 
    eval "function $(printf %q "$2") ${a#*"()"}" && 
    unset -f "$1"; 
} 

Ahora bien, esto atrapa este caso artificial. (Tenga en cuenta que printf con %q es una orden interna del bash.)

Por supuesto se puede dividir esto en la copia + cambiar el nombre de la siguiente manera:

copy_fn() { local a; a="$(declare -f "$1")" && eval "function $(printf %q "$2") ${a#*"()"}"; } 
rename_fn() { copy_fn "[email protected]" && unset -f "$1"; } 

espero que esta es la solución 101%. Si necesita una mejora, coméntelo;)

0

Para aquellos de nosotros forzados a ser compatibles con bash 3.2 (usted sabe de quién estamos hablando), declare -f no funciona.He encontrado type puede trabajar

eval "$(type my_func | sed $'1d;2c\\\nmy_func_copy()\n')" 

En forma de función, que se vería así

copy_function() 
{ 
    eval "$(type "${1}"| sed $'1d;2c\\\n'"${2}"$'()\n')" 
} 

Y si realmente quiere no depender de 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}"))" 
} 

pero eso es una un poco prolijo para mí

Cuestiones relacionadas