Bash: passer une fonction en paramètre


91

J'ai besoin de passer une fonction en paramètre dans Bash. Par exemple, le code suivant:

function x() {
  echo "Hello world"
}

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

around x

Devrait afficher:

before
Hello world
after

Je sais que ce evaln'est pas correct dans ce contexte, mais ce n'est qu'un exemple :)

Une idée?

Réponses:


126

Si vous n'avez besoin de rien d'extraordinaire comme retarder l'évaluation du nom de la fonction ou de ses arguments, vous n'avez pas besoin de eval:

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

around x

fait ce que vous voulez. Vous pouvez même passer la fonction et ses arguments de cette façon:

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

around x 1st 2nd

impressions

before
x(): Passed 1st and 2nd
after

2
Si j'ai une autre fonction y (), puis-je faire environ x 1er 2e y 1er 2e? Comment sait-il que x et y sont des arguments pour environ tandis que les 1er et 2e sont des arguments pour x et y?
techguy2000

Et vous pouvez omettre le mot de fonction.
jasonleonhard

Cependant, ils ne seront pas espacés de nom. c'est-à-dire que si vous aviez des méthodes à l'intérieur des méthodes et que vous gardez le functionmot, vous ne pouvez pas accéder à ces méthodes internes tant que vous n'avez pas exécuté la méthode de niveau supérieur.
jasonleonhard

29

Je ne pense pas que quiconque ait tout à fait répondu à la question. Il n'a pas demandé s'il pouvait faire écho aux chaînes dans l'ordre. Au contraire, l'auteur de la question veut savoir s'il peut simuler le comportement du pointeur de fonction.

Il y a quelques réponses qui ressemblent beaucoup à ce que je ferais, et je veux les développer avec un autre exemple.

De l'auteur:

function x() {
  echo "Hello world"
}

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

around x

Pour développer cela, nous aurons la fonction x echo "Hello world: $ 1" pour montrer quand l'exécution de la fonction se produit réellement. Nous allons passer une chaîne qui est le nom de la fonction "x":

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

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

around x

Pour décrire cela, la chaîne "x" est passée à la fonction around () qui fait écho "before", appelle la fonction x (via la variable $ 1, le premier paramètre passé à around) en passant l'argument "HERE", enfin écho après .

En outre, c'est la méthodologie pour utiliser des variables comme noms de fonction. Les variables contiennent en fait la chaîne qui est le nom de la fonction et ($ variable arg1 arg2 ...) appelle la fonction en passant les arguments. Voir ci-dessous:

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

Z="x"        # or just Z=x

($Z  10 20 30)

donne: 30 10 20, où nous avons exécuté la fonction nommée "x" stockée dans la variable Z et passé les paramètres 10 20 et 30.

Ci-dessus où nous référençons les fonctions en attribuant des noms de variables aux fonctions afin que nous puissions utiliser la variable au lieu de connaître réellement le nom de la fonction (ce qui est similaire à ce que vous pourriez faire dans une situation de pointeur de fonction très classique en c pour généraliser le flux du programme mais pré -sélectionner les appels de fonction que vous allez effectuer en fonction des arguments de la ligne de commande).

Dans bash, ce ne sont pas des pointeurs de fonction, mais des variables qui font référence à des noms de fonctions que vous utiliserez plus tard.


Cette réponse est géniale. J'ai fait des scripts bash de tous les exemples et les ai exécutés. J'aime aussi beaucoup la façon dont vous avez fait des créateurs de "only change" qui ont beaucoup aidé. Votre avant-dernier paragraphe est mal orthographié: "Aabove"
JMI MADISON

Erreur de frappe corrigée. Merci @J MADISON
uDude

7
pourquoi l'enveloppez-vous, cela ()ne démarre-t- il pas un sous-shell?
horseyguy

17

il n'y a pas besoin d'utiliser eval

function x() {
  echo "Hello world"
}

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

around x

5

Vous ne pouvez rien transmettre à une fonction autre que des chaînes. Les substitutions de processus peuvent en quelque sorte faire semblant. Bash a tendance à maintenir le FIFO ouvert jusqu'à ce qu'une commande soit étendue pour se terminer.

Voici un rapide idiot

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)

Les fonctions peuvent être exportées, mais ce n'est pas aussi intéressant qu'il n'y paraît. Je trouve que c'est principalement utile pour rendre les fonctions de débogage accessibles aux scripts ou à d'autres programmes qui exécutent des scripts.

(
    id() {
        "$@"
    }

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

id obtient toujours uniquement une chaîne qui se trouve être le nom d'une fonction (importée automatiquement à partir d'une sérialisation dans l'environnement) et ses arguments.

Le commentaire de Pumbaa80 à une autre réponse est également bon ( eval $(declare -F "$1")), mais il est principalement utile pour les tableaux, pas pour les fonctions, car ils sont toujours globaux. Si vous l'exécutiez dans une fonction, tout ce qu'il ferait serait de la redéfinir, donc il n'y a aucun effet. Il ne peut pas être utilisé pour créer des fermetures ou des fonctions partielles ou des "instances de fonction" dépendant de ce qui se trouve être lié dans la portée actuelle. Au mieux, cela peut être utilisé pour stocker une définition de fonction dans une chaîne qui est redéfinie ailleurs - mais ces fonctions ne peuvent également être codées en dur que si bien sûr evalest utilisé

Fondamentalement, Bash ne peut pas être utilisé comme ça.


2

Une meilleure approche consiste à utiliser des variables locales dans vos fonctions. Le problème est alors de savoir comment obtenir le résultat pour l'appelant. Un mécanisme consiste à utiliser la substitution de commande:

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

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

Ici, le résultat est sorti vers la sortie standard et l'appelant utilise la substitution de commande pour capturer la valeur dans une variable. La variable peut ensuite être utilisée selon les besoins.


1

Vous devriez avoir quelque chose du genre:

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

Vous pouvez alors appeler around x


-1

eval est probablement le seul moyen d'y parvenir. Le seul vrai inconvénient est l'aspect sécurité, car vous devez vous assurer que rien de malveillant n'est transmis et que seules les fonctions que vous voulez être appelées seront appelées (en plus de vérifier qu'il n'y a pas de mauvais caractères comme ';' dedans aussi).

Donc, si c'est vous qui appelez le code, alors eval est probablement le seul moyen de le faire. Notez qu'il existe d'autres formes d'évaluation qui fonctionneraient probablement aussi avec des sous-commandes ($ () et ``), mais elles ne sont pas plus sûres et sont plus chères.


eval est la seule façon de le faire.
Wes du

1
Vous pouvez facilement vérifier si vous eval $1souhaitez appeler une fonction en utilisantif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... ou mieux encore:eval $(declare -F "$1")
user123444555621
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.