Bash: passa una funzione come parametro


92

Devo passare una funzione come parametro in Bash. Ad esempio, il codice seguente:

function x() {
  echo "Hello world"
}

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

around x

Dovrebbe produrre:

before
Hello world
after

So che evalnon è corretto in quel contesto, ma questo è solo un esempio :)

Qualche idea?

Risposte:


128

Se non hai bisogno di niente di speciale come ritardare la valutazione del nome della funzione o dei suoi argomenti, non hai bisogno di eval:

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

around x

fa quello che vuoi. Puoi anche passare la funzione e i suoi argomenti in questo modo:

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

around x 1st 2nd

stampe

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

2
Se ho un'altra funzione y (), posso fare circa x 1 ° 2 ° y 1 ° 2 °? Come fa a sapere che xey sono argomenti per intorno mentre 1st e 2nd sono argomenti per xey?
techguy2000

E puoi omettere la parola funzione.
jasonleonhard

Tuttavia, non saranno spaziati tra i nomi. Ad esempio, se si functiondispone di metodi all'interno di metodi e si mantiene la parola non è possibile accedere a tali metodi interni finché non si esegue il metodo di livello superiore.
jasonleonhard

29

Non credo che qualcuno abbia risposto del tutto alla domanda. Non ha chiesto se poteva ripetere le stringhe in ordine. Piuttosto, l'autore della domanda vuole sapere se può simulare il comportamento del puntatore di funzione.

Ci sono un paio di risposte che sono molto simili a quello che farei io e voglio espanderle con un altro esempio.

Dall'autore:

function x() {
  echo "Hello world"
}

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

around x

Per espandere questo, avremo la funzione x echo "Hello world: $ 1" per mostrare quando si verifica realmente l'esecuzione della funzione. Passeremo una stringa che è il nome della funzione "x":

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

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

around x

Per descriverlo, la stringa "x" viene passata alla funzione around () che echi "prima", chiama la funzione x (tramite la variabile $ 1, il primo parametro passato a around) passando l'argomento "QUI", infine echi dopo .

Inoltre, questa è la metodologia per utilizzare le variabili come nomi di funzioni. Le variabili effettivamente contengono la stringa che è il nome della funzione e ($ variabile arg1 arg2 ...) chiama la funzione passando gli argomenti. Vedi sotto:

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, dove abbiamo eseguito la funzione denominata "x" memorizzata nella variabile Z e passato i parametri 10 20 e 30.

Sopra dove facciamo riferimento alle funzioni assegnando nomi di variabili alle funzioni in modo che possiamo usare la variabile al posto di conoscere effettivamente il nome della funzione (che è simile a quello che potresti fare in una situazione molto classica del puntatore di funzione in c per generalizzare il flusso del programma ma pre -selezionando le chiamate di funzione che farai in base agli argomenti della riga di comando).

In bash questi non sono puntatori a funzioni, ma variabili che fanno riferimento a nomi di funzioni che userete successivamente.


Questa risposta è fantastica. Ho creato script bash di tutti gli esempi e li ho eseguiti. Mi piace molto anche come hai creato creatori di "solo cambiamento", che ha aiutato molto. Il penultimo paragrafo ha un'ortografia errata: "Aabove"
twitchdotcom slash KANJICODER

Errore di battitura corretto. Grazie @J MADISON
uDude

7
perché lo avvolgi ()non inizierà una sotto-shell?
horseyguy

17

non c'è bisogno di usare eval

function x() {
  echo "Hello world"
}

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

around x

5

Non puoi passare nulla a una funzione diversa dalle stringhe. Le sostituzioni di processo possono in qualche modo fingere. Bash tende a tenere aperto il FIFO fino al completamento di un comando.

Eccone uno sciocco veloce

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)

Le funzioni possono essere esportate, ma non è così interessante come sembra. Trovo che sia principalmente utile per rendere le funzioni di debug accessibili agli script o ad altri programmi che eseguono script.

(
    id() {
        "$@"
    }

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

id ottiene ancora solo una stringa che sembra essere il nome di una funzione (importata automaticamente da una serializzazione nell'ambiente) e i suoi argomenti.

Anche il commento di Pumbaa80 a un'altra risposta è buono ( eval $(declare -F "$1")), ma è principalmente utile per gli array, non per le funzioni, poiché sono sempre globali. Se dovessi eseguirlo all'interno di una funzione, tutto ciò che farebbe è ridefinirlo, quindi non c'è alcun effetto. Non può essere utilizzato per creare chiusure o funzioni parziali o "istanze di funzione" dipendenti da qualsiasi cosa sia vincolata nell'ambito corrente. Nella migliore delle ipotesi questo può essere utilizzato per memorizzare una definizione di funzione in una stringa che viene ridefinita altrove, ma anche quelle funzioni possono essere codificate solo a meno che, ovviamente, non vengano evalutilizzate

Fondamentalmente Bash non può essere usato in questo modo.


2

Un approccio migliore è utilizzare le variabili locali nelle funzioni. Il problema quindi diventa come ottenere il risultato al chiamante. Un meccanismo consiste nell'utilizzare la sostituzione del comando:

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

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

Qui il risultato viene inviato allo stdout e il chiamante utilizza la sostituzione del comando per acquisire il valore in una variabile. La variabile può quindi essere utilizzata secondo necessità.


1

Dovresti avere qualcosa sulla falsariga di:

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

Puoi quindi chiamare around x


-1

eval è probabilmente l'unico modo per ottenerlo. L'unico vero svantaggio è l'aspetto della sicurezza, in quanto devi assicurarti che non venga passato nulla di dannoso e che vengano chiamate solo le funzioni che vuoi essere chiamate (oltre a controllare che non abbia caratteri sgradevoli come ";" anche in esso).

Quindi, se sei tu a chiamare il codice, è probabile che eval sia l'unico modo per farlo. Nota che ci sono altre forme di eval che probabilmente funzionerebbero anche coinvolgendo i sottocomandi ($ () e ``), ma non sono più sicure e sono più costose.


eval è l'unico modo per farlo.
Wes

1
Puoi facilmente verificare se eval $1chiamare una funzione utilizzandoif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... o anche meglio:eval $(declare -F "$1")
user123444555621
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.