Bash: Übergeben Sie eine Funktion als Parameter

87

Ich muss eine Funktion als Parameter in Bash übergeben. Zum Beispiel der folgende Code:

function x() {
  echo "Hello world"
}

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

around x

Sollte ausgeben:

before
Hello world
after

Ich weiß, dass dies evalnicht korrekt ist, aber das ist nur ein Beispiel :)

Irgendeine Idee?

cd1
quelle

Antworten:

120

Wenn Sie nichts Besonderes brauchen, wie die Auswertung des Funktionsnamens oder seiner Argumente zu verzögern, brauchen Sie nicht eval:

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

around x

macht was du willst. Sie können die Funktion und ihre Argumente sogar folgendermaßen übergeben:

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

around x 1st 2nd

druckt

before
x(): Passed 1st and 2nd
after
Idelic
quelle
2
Wenn ich eine andere y () -Funktion habe, kann ich ungefähr x 1st 2nd y 1st 2nd ausführen? Woher weiß es, dass x und y Argumente für ungefähr sind, während 1. und 2. Argumente für x und y sind?
Techguy2000
Und Sie können das Funktionswort weglassen.
Jasonleonhard
Dann haben sie jedoch keinen Namensabstand. Wenn Sie Methoden innerhalb von Methoden hatten und das functionWort behalten , können Sie erst dann auf diese internen Methoden zugreifen, wenn Sie die Methode der obersten Ebene ausführen.
Jasonleonhard
28

Ich glaube nicht, dass jemand die Frage ganz beantwortet hat. Er fragte nicht, ob er die Saiten in der richtigen Reihenfolge wiedergeben könne. Vielmehr möchte der Autor der Frage wissen, ob er das Verhalten von Funktionszeigern simulieren kann.

Es gibt einige Antworten, die meinen Vorstellungen sehr ähnlich sind, und ich möchte sie um ein weiteres Beispiel erweitern.

Vom Autor:

function x() {
  echo "Hello world"
}

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

around x

Um dies zu erweitern, wird die Funktion x echo "Hello world: $ 1" angezeigt, um anzuzeigen, wann die Funktionsausführung tatsächlich erfolgt. Wir werden eine Zeichenfolge übergeben, die der Name der Funktion "x" ist:

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

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

around x

Um dies zu beschreiben, wird die Zeichenfolge "x" an die Funktion around () übergeben, die "before" wiedergibt, die Funktion x (über die Variable $ 1, den ersten Parameter, der an around übergeben wird) aufruft und das Argument "HERE" übergibt .

Abgesehen davon ist dies die Methode, um Variablen als Funktionsnamen zu verwenden. Die Variablen enthalten tatsächlich die Zeichenfolge, die der Name der Funktion ist, und ($ variable arg1 arg2 ...) ruft die Funktion auf, die die Argumente übergibt. Siehe unten:

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

Z="x"        # or just Z=x

($Z  10 20 30)

ergibt: 30 10 20, wobei wir die in der Variablen Z gespeicherte Funktion "x" ausgeführt und die Parameter 10 20 und 30 übergeben haben.

Oben, wo wir Funktionen referenzieren, indem wir den Funktionen Variablennamen zuweisen, damit wir die Variable verwenden können, anstatt den Funktionsnamen tatsächlich zu kennen (ähnlich wie Sie es in einer sehr klassischen Funktionszeigersituation in c tun könnten, um den Programmfluss zu verallgemeinern, aber vor - Auswahl der Funktionsaufrufe, die Sie basierend auf Befehlszeilenargumenten ausführen).

In Bash sind dies keine Funktionszeiger, sondern Variablen, die sich auf Namen von Funktionen beziehen, die Sie später verwenden.

uDude
quelle
Diese Antwort ist großartig. Ich habe aus allen Beispielen Bash-Skripte erstellt und sie ausgeführt. Ich mag auch sehr, wie Sie Macher von "nur Veränderung" gemacht haben, was sehr geholfen hat. Ihr vorletzter Absatz hat eine falsche Schreibweise: "Aabove"
JMI MADISON
Tippfehler behoben. Vielen Dank @J MADISON
uDude
6
Warum wickelst du es ein, ()startet das nicht eine Sub-Shell?
Horseyguy
16

es besteht keine Notwendigkeit zu verwenden eval

function x() {
  echo "Hello world"
}

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

around x
kurumi
quelle
5

Sie können nichts anderes als Zeichenfolgen an eine andere Funktion übergeben. Prozessersetzungen können es irgendwie vortäuschen. Bash neigt dazu, das FIFO offen zu halten, bis ein Befehl abgeschlossen ist.

Hier ist eine kurze dumme

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)

Funktionen können exportiert werden, dies ist jedoch nicht so interessant, wie es zunächst erscheint. Ich finde es hauptsächlich nützlich, um Debugging-Funktionen für Skripte oder andere Programme, die Skripte ausführen, zugänglich zu machen.

(
    id() {
        "$@"
    }

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

id Es wird immer noch nur eine Zeichenfolge abgerufen, die zufällig der Name einer Funktion (automatisch aus einer Serialisierung in der Umgebung importiert) und deren Argumente ist.

Der Kommentar von Pumbaa80 zu einer anderen Antwort ist ebenfalls gut ( eval $(declare -F "$1")), aber hauptsächlich für Arrays nützlich, nicht für Funktionen, da sie immer global sind. Wenn Sie dies innerhalb einer Funktion ausführen würden, würde es nur neu definiert, sodass es keine Auswirkungen gibt. Es kann nicht verwendet werden, um Abschlüsse oder Teilfunktionen oder "Funktionsinstanzen" zu erstellen, abhängig davon, was im aktuellen Bereich gebunden ist. Bestenfalls kann dies verwendet werden, um eine Funktionsdefinition in einer Zeichenfolge zu speichern, die an anderer Stelle neu definiert wird - aber diese Funktionen können auch nur fest codiert werden, es sei denn natürlicheval verwendet werden

Grundsätzlich kann Bash nicht so verwendet werden.

Ormaaj
quelle
2

Ein besserer Ansatz ist die Verwendung lokaler Variablen in Ihren Funktionen. Das Problem wird dann, wie Sie das Ergebnis zum Anrufer erhalten. Ein Mechanismus ist die Verwendung der Befehlssubstitution:

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

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

Hier wird das Ergebnis an stdout ausgegeben und der Aufrufer verwendet die Befehlssubstitution, um den Wert in einer Variablen zu erfassen. Die Variable kann dann nach Bedarf verwendet werden.

Anand Thangappan
quelle
1

Sie sollten etwas in der Art haben:

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

Sie können dann anrufen around x

Tim O.
quelle
-1

eval ist wahrscheinlich der einzige Weg, dies zu erreichen. Der einzige wirkliche Nachteil ist der Sicherheitsaspekt, da Sie sicherstellen müssen, dass nichts Bösartiges weitergegeben wird und nur Funktionen aufgerufen werden, die aufgerufen werden sollen (zusammen mit der Überprüfung, dass keine bösen Zeichen wie ';' vorhanden sind). auch darin).

Wenn Sie also derjenige sind, der den Code aufruft, ist eval wahrscheinlich der einzige Weg, dies zu tun. Beachten Sie, dass es andere Formen der Bewertung gibt, die wahrscheinlich auch mit Unterbefehlen ($ () und ``) funktionieren würden, aber sie sind nicht sicherer und teurer.

Wes Hardaker
quelle
eval ist der einzige Weg, dies zu tun.
Wes
1
Sie können leicht überprüfen, ob eval $1eine Funktion mitif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621
2
... oder noch besser:eval $(declare -F "$1")
user123444555621