Bash: pasar una función como parámetro

91

Necesito pasar una función como parámetro en Bash. Por ejemplo, el siguiente código:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  eval $1
  echo "after"
}

around x

Debería generar:

before
Hello world
after

evalque no es correcto en ese contexto, pero eso es solo un ejemplo :)

¿Alguna idea?

cd1
fuente

Respuestas:

126

Si no necesita nada sofisticado como retrasar la evaluación del nombre de la función o sus argumentos, no necesita eval:

function x()      { echo "Hello world";          }
function around() { echo before; $1; echo after; }

around x

hace lo que quiere. Incluso puede pasar la función y sus argumentos de esta manera:

function x()      { echo "x(): Passed $1 and $2";  }
function around() { echo before; "$@"; echo after; }

around x 1st 2nd

huellas dactilares

before
x(): Passed 1st and 2nd
after
Idelic
fuente
2
Si tengo otra función y (), ¿puedo hacer alrededor de x 1st 2nd y 1st 2nd? ¿Cómo sabe que xey son argumentos para alrededor, mientras que el primero y el segundo son argumentos para x e y?
techguy2000
Y puede omitir la palabra funcional.
jasonleonhard
Sin embargo, entonces no estarán espaciados. es decir, si tenía métodos dentro de los métodos y conserva la functionpalabra, no podrá acceder a esos métodos internos hasta que ejecute el método de nivel superior.
jasonleonhard
29

No creo que nadie haya respondido bien a la pregunta. No preguntó si podía hacer eco de las cuerdas en orden. Más bien, el autor de la pregunta quiere saber si puede simular el comportamiento del puntero de función.

Hay un par de respuestas que se parecen mucho a lo que haría yo, y quiero ampliarlas con otro ejemplo.

Del autor:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  ($1)                   <------ Only change
  echo "after"
}

around x

Para expandir esto, tendremos la función x echo "Hola mundo: $ 1" para mostrar cuándo ocurre realmente la ejecución de la función. Pasaremos una cadena que es el nombre de la función "x":

function x() {
  echo "Hello world:$1"
}

function around() {
  echo "before"
  ($1 HERE)                   <------ Only change
  echo "after"
}

around x

Para describir esto, la cadena "x" se pasa a la función around () que echos "antes", llama a la función x (a través de la variable $ 1, el primer parámetro pasado a around) pasando el argumento "AQUÍ", finalmente echos después .

Como otro aparte, esta es la metodología para usar variables como nombres de funciones. Las variables realmente contienen la cadena que es el nombre de la función y ($ variable arg1 arg2 ...) llama a la función pasando los argumentos. Vea abajo:

function x(){
    echo $3 $1 $2      <== just rearrange the order of passed params
}

Z="x"        # or just Z=x

($Z  10 20 30)

da: 30 10 20, donde ejecutamos la función llamada "x" almacenada en la variable Z y pasamos los parámetros 10 20 y 30.

Arriba, donde hacemos referencia a funciones asignando nombres de variable a las funciones para que podamos usar la variable en lugar de conocer realmente el nombre de la función (que es similar a lo que podría hacer en una situación de puntero de función muy clásica en c para generalizar el flujo del programa pero antes -seleccionando las llamadas a la función que realizará en función de los argumentos de la línea de comandos).

En bash, estos no son punteros a funciones, sino variables que hacen referencia a nombres de funciones que luego usa.

uDude
fuente
Esta respuesta es asombrosa. Hice scripts bash de todos los ejemplos y los ejecuté. También me gustó mucho cómo hiciste creadores de "solo cambio", lo cual ayudó mucho. El penúltimo párrafo tiene un error de ortografía: "Aabove"
JMI MADISON
Error tipográfico corregido. Gracias @J MADISON
uDude
7
¿Por qué lo envuelves (), no iniciará un sub-shell?
Horseyguy
17

no hay necesidad de usar eval

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  var=$($1)
  echo "after $var"
}

around x
Kurumi
fuente
5

No puede pasar nada a una función que no sean cadenas. Las sustituciones de procesos pueden simularlo. Bash tiende a mantener abierto el FIFO hasta que se completa un comando que se expande.

Aquí hay uno rápido y tonto

foldl() {
    echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)

# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)

Las funciones se pueden exportar, pero esto no es tan interesante como parece. Encuentro que es principalmente útil para hacer que las funciones de depuración sean accesibles para scripts u otros programas que ejecutan scripts.

(
    id() {
        "$@"
    }

    export -f id
    exec bash -c 'echowrap() { echo "$1"; }; id echowrap hi'
)

id todavía solo obtiene una cadena que resulta ser el nombre de una función (importada automáticamente desde una serialización en el entorno) y sus argumentos.

El comentario de Pumbaa80 a otra respuesta también es bueno ( eval $(declare -F "$1")), pero es principalmente útil para matrices, no funciones, ya que siempre son globales. Si ejecutara esto dentro de una función, todo lo que haría es redefinirlo, por lo que no tiene ningún efecto. No se puede utilizar para crear cierres o funciones parciales o "instancias de función" dependientes de lo que sea que esté vinculado en el ámbito actual. En el mejor de los casos, esto se puede usar para almacenar una definición de función en una cadena que se redefine en otro lugar, pero esas funciones solo se pueden codificar a menos que, por supuesto, evalse utilicen

Básicamente, Bash no se puede usar así.

ormaaj
fuente
2

Un mejor enfoque es usar variables locales en sus funciones. Entonces, el problema se convierte en cómo hacer llegar el resultado a la persona que llama. Un mecanismo es utilizar la sustitución de comandos:

function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

result=$(myfunc)   # or result=`myfunc`
echo $result

Aquí el resultado se envía a la salida estándar y la persona que llama utiliza la sustitución de comandos para capturar el valor en una variable. Luego, la variable se puede usar según sea necesario.

Anand Thangappan
fuente
1

Deberías tener algo parecido a:

function around()
{
  echo 'before';
  echo `$1`;
  echo 'after';
}

Entonces puedes llamar around x

Tim O
fuente
-1

eval es probablemente la única forma de lograrlo. El único inconveniente real es el aspecto de seguridad, ya que debe asegurarse de que no se pase nada malicioso y solo se invoquen las funciones que desea que se llamen (además de verificar que no tenga caracteres desagradables como ';' en él también).

Entonces, si eres el que llama al código, entonces eval es probablemente la única forma de hacerlo. Tenga en cuenta que hay otras formas de evaluación que probablemente también funcionarían con subcomandos ($ () y ``), pero no son más seguras y son más caras.

Wes Hardaker
fuente
eval es la única forma de hacerlo.
Wes
1
Puede verificar fácilmente si eval $1llamaría a una función usandoif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621
2
... o incluso mejor:eval $(declare -F "$1")
user123444555621