Bash: passa uma função como parâmetro

91

Preciso passar uma função como parâmetro no Bash. Por exemplo, o seguinte código:

function x() {
  echo "Hello world"
}

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

around x

Deve produzir:

before
Hello world
after

Eu sei que evalnão está correto nesse contexto, mas isso é apenas um exemplo :)

Qualquer ideia?

cd1
fonte

Respostas:

126

Se você não precisa de nada sofisticado, como atrasar a avaliação do nome da função ou de seus argumentos, não precisa eval:

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

around x

faz o que você quiser. Você pode até mesmo passar a função e seus argumentos desta forma:

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

around x 1st 2nd

estampas

before
x(): Passed 1st and 2nd
after
Idelic
fonte
2
Se eu tiver outra função y (), posso fazer em torno de x 1º 2º y 1º 2º? Como ele sabe que xey são argumentos para cerca de enquanto o primeiro e o segundo são argumentos para x e y?
techguy2000
E você pode omitir a palavra de função.
jasonleonhard
No entanto, eles não terão espaço entre nomes. ou seja, se você tiver métodos dentro de métodos e mantiver a functionpalavra, não poderá acessar esses métodos internos até executar o método de nível superior.
jasonleonhard
29

Acho que ninguém respondeu exatamente à pergunta. Ele não perguntou se ele poderia ecoar cordas em ordem. Em vez disso, o autor da pergunta deseja saber se ele pode simular o comportamento do ponteiro de função.

Existem algumas respostas que são muito parecidas com o que eu faria e quero expandi-las com outro exemplo.

Do autor:

function x() {
  echo "Hello world"
}

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

around x

Para expandir isso, teremos a função x echo "Hello world: $ 1" para mostrar quando a execução da função realmente ocorre. Vamos passar uma string que é o nome da função "x":

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

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

around x

Para descrever isso, a string "x" é passada para a função around () que ecoa "antes", chama a função x (através da variável $ 1, o primeiro parâmetro passado para around) passando o argumento "AQUI" e finalmente ecoa depois .

Como outro aparte, esta é a metodologia para usar variáveis ​​como nomes de funções. As variáveis ​​realmente contêm a string que é o nome da função e ($ variable arg1 arg2 ...) chama a função passando os argumentos. Ver abaixo:

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

Z="x"        # or just Z=x

($Z  10 20 30)

dá: 30 10 20, onde executamos a função chamada "x" armazenada na variável Z e passamos os parâmetros 10 20 e 30.

Acima, onde referimos funções atribuindo nomes de variáveis ​​às funções para que possamos usar a variável no lugar de realmente saber o nome da função (que é semelhante ao que você pode fazer em uma situação de ponteiro de função muito clássica em c para generalizar o fluxo do programa, mas antes -selecionando as chamadas de função que você fará com base nos argumentos da linha de comando).

No bash, esses não são ponteiros de função, mas variáveis ​​que se referem a nomes de funções que você usará posteriormente.

uDude
fonte
Essa resposta é incrível. Fiz scripts bash de todos os exemplos e os executei. Eu também gosto muito de como vocês criaram "apenas mudanças", o que ajudou muito. Seu penúltimo parágrafo tem uma grafia incorreta: "Aabove"
JMI MADISON
Erro de digitação corrigido. Obrigado @J MADISON
uDude
7
Por que você o embrulha, isso ()não iniciará um sub-shell?
horseyguy
17

não há necessidade de usar eval

function x() {
  echo "Hello world"
}

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

around x
kurumi
fonte
5

Você não pode passar nada para uma função diferente de strings. As substituições de processos podem meio que falsificá-lo. O Bash tende a manter aberto o FIFO até que um comando seja expandido para ser concluído.

Aqui está um rápido e bobo

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)

As funções podem ser exportadas, mas isso não é tão interessante quanto parece à primeira vista. Acho que é principalmente útil para tornar as funções de depuração acessíveis a scripts ou outros programas que executam scripts.

(
    id() {
        "$@"
    }

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

id ainda obtém apenas uma string que é o nome de uma função (importada automaticamente de uma serialização no ambiente) e seus args.

O comentário de Pumbaa80 para outra resposta também é bom ( eval $(declare -F "$1")), mas é útil principalmente para arrays, não funções, já que eles são sempre globais. Se você executasse isso dentro de uma função, tudo o que faria seria redefini-lo, portanto, não teria efeito. Não pode ser usado para criar fechamentos ou funções parciais ou "instâncias de função" dependendo do que quer que esteja vinculado no escopo atual. Na melhor das hipóteses, isso pode ser usado para armazenar uma definição de função em uma string que é redefinida em outro lugar - mas essas funções também podem ser codificadas somente a menos que evalseja usado

Basicamente, o Bash não pode ser usado dessa forma.

Ormaaj
fonte
2

Uma abordagem melhor é usar variáveis ​​locais em suas funções. O problema passa a ser como você entrega o resultado ao chamador. Um mecanismo é usar a substituição de comando:

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

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

Aqui, o resultado é enviado para o stdout e o chamador usa a substituição de comando para capturar o valor em uma variável. A variável pode então ser usada conforme necessário.

Anand Thangappan
fonte
1

Você deve ter algo como:

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

Você pode então ligar around x

Tim O
fonte
-1

eval é provavelmente a única forma de o conseguir. A única desvantagem real é o aspecto de segurança, pois você precisa se certificar de que nada malicioso seja transmitido e apenas as funções que você deseja que sejam chamadas sejam chamadas (junto com a verificação de que não haja caracteres desagradáveis ​​como ';' nele também).

Portanto, se você é quem está chamando o código, provavelmente eval é a única maneira de fazê-lo. Observe que existem outras formas de avaliação que provavelmente funcionariam também envolvendo subcomandos ($ () e ``), mas não são mais seguras e são mais caras.

Wes Hardaker
fonte
eval é a única maneira de fazer isso.
Wes,
1
Você pode facilmente verificar se eval $1chamaria uma função usandoif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621
2
... ou ainda melhor:eval $(declare -F "$1")
user123444555621