Bash - Funktionen in Shell-Variablen

7

Ich lese diesen Beitrag über die Verwendung von Funktionen in Bash-Shell-Variablen. Ich beobachte, dass man Shell-Funktionen wie folgt exportieren und in einer untergeordneten Shell ausführen muss, um sie zu verwenden:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Ich möchte fragen, ob es möglich ist, Funktionen in Bash-Shell-Variablen so zu definieren, dass sie in der aktuellen Shell ausgeführt werden können, ohne exportieren und eine neue Shell ausführen zu müssen.

Jake
quelle
3
Was ist los mit nur foo() { echo "Inside function"; }; foo?
Cuonglm
Nein, ich meinte, wenn es möglich wäre, es zu definieren und in derselben Shell auszuführen, aber zu einem späteren Zeitpunkt (etwa nach dem Ausführen einiger anderer Befehle)
Jake
1
Die obige Syntax definiert sie genau und führt sie später aus . Definieren Sie die Funktion einfach so wie sie ist. Sie müssen sie nur exportieren, wenn Sie möchten, dass sie in der Sub-Shell verfügbar ist. Und beachten Sie, dass Ihr Beispiel nach dem Beheben des Shellshocks nicht mehr funktioniert. Sie müssen die Funktion definieren und dann explizit exportieren.
Cuonglm
Wenn Sie der Funktion einen Namen geben. $ function bar() { echo "Inside function"; } $ foo="bar" $ ${foo} Inside function $
Andy Dalton

Antworten:

8

Nein, das kannst du nicht.

Wenn Sie eine Funktion in der aktuellen Shell verwenden möchten, definieren Sie sie einfach und verwenden Sie sie später:

$ foo() { echo "Inside function"; }
$ foo
Inside function

Vor dem Shellshock- Tag können Sie Funktionen in einer Variablen speichern, Variablen exportieren und Funktionen in Sub-Shell verwenden, da die bashUnterstützung der Exportfunktion die Funktionsdefinition in Umgebungsvariablen wie bashfolgt einfügt :

foo=() {
  echo "Inside function"
}

Interpretieren Sie dann die Funktion, indem Sie sie durch =Leerzeichen ersetzen . Es besteht keine Absicht, eine Funktion in eine Variable einzufügen, und es wird auf eine Variable verwiesen, die die Funktion ausführt.

Nachdem Stéphane Chazelas den Fehler gefunden hatte , wurde diese Funktion entfernt und keine Funktionsdefinition mehr in einer einfachen Umgebungsvariablen gespeichert. Jetzt werden exportierte Funktionen durch Anhängen von Präfix BASH_FUNC_und Suffix codiert %%, um Konflikte mit Umgebungsvariablen zu vermeiden. bashDer Interpreter kann nun unabhängig vom Inhalt der Variablen feststellen, ob es sich um eine Shell-Funktion handelt. Sie müssen die Funktion auch definieren und explizit exportieren, um sie in der Sub-Shell zu verwenden:

$ foo() { echo "Inside function"; }
$ export -f foo
$ bash -c foo
Inside function

Wenn Ihr Beispiel mit Ihrer aktuellen bashVersion funktioniert hat , verwenden Sie eine anfällige Version.

cuonglm
quelle
2
Übrigens: Diese Funktion stand immer im Widerspruch zum POSIX-Standard.
schily
1
Ich habe das vor kurzem export, unexportund unsetbuiltins von der Bourne - Shell POSIX - kompatibel und irgendwo in dieser builtins ist die Erklärung, ich glaube an export.
schily
1
@cuonglm Gibt es eine Manpage, die beschreibt, wie Funktionen in Bash verwendet werden?
Jake
1
@ Jake - man bash. Wenn Sie es öffnen, drücken Sie die /Taste und verwenden Sie einen regulären ^[[:space:]]*FuncAusdruck, um eine Zeile zu finden, die mit dieser Phrase beginnt. Drücken Sie n, um Übereinstimmungen zu Ihrer Suche zu überspringen. und nach Ihrer Frage zu urteilen, möchten Sie vielleicht auch einen Blick darauf werfen alias.
Mikeserv
1
@Jake: Das Semikolon ist nur ein Terminator, der zum Trennen des Shell-Tokens verwendet wird. Hier ist der Befehl und das }. bashDie Dokumentation enthält einen Detailteil über Funktionen .
Cuonglm
5
foo='foo(){ echo "Inside Function"; }'
bash -c "$foo; foo"

Inside Function

Eine Funktion ist schließlich einfach eine benannte, vorab analysierte statische Befehlszeichenfolge, die im Speicher der Shell gespeichert ist. Der einzige wirkliche Weg, dies zu tun, besteht darin, Zeichenfolgen als Code auszuwerten. Genau das tut die Shell jedes Mal, wenn Sie Ihre Funktion aufrufen.

Es war noch nie so anders und ist es immer noch nicht. Die Entwickler richten hierfür bequeme Methoden ein und versuchen, mögliche Sicherheitslücken so gut wie möglich zu schützen. Tatsache ist jedoch, dass je bequemer eine Sache wird, desto wahrscheinlicher ist es, dass Sie weniger darüber wissen, als Sie sollten.

Es gibt viele Optionen zur Verfügung , wenn es darum geht , Daten von den Eltern Schalen Subshells zu importieren. Machen Sie sich mit ihnen vertraut, werden Sie sicher genug, dass Sie ihre potenziellen Verwendungszwecke und ihre potenziellen Schwächen identifizieren und sie sparsam, aber effektiv einsetzen können.


Ich nehme an, ich kann ein oder zwei davon näher erläutern, während ich dabei bin. Der wahrscheinlich beste Weg, um statische Befehle von einer Shell an eine andere zu übergeben (egal ob es sich um eine untergeordnete Befehlskette handelt oder nicht), ist alias. aliasist in dieser Funktion nützlich, da es POSIX-spezifiziert ist , um seine definierten Befehle sicher zu melden :

Das Format für die Anzeige von Aliasen (wenn keine Operanden oder nur Namensoperanden angegeben sind) muss sein:

  • "%s=%s\n", name, value

Die Wertzeichenfolge muss mit entsprechenden Anführungszeichen geschrieben werden, damit sie für die erneute Eingabe in die Shell geeignet ist. Siehe die Beschreibung des Shell-Zitierens in Quoting .

Zum Beispiel:

alias foo="foo(){ echo 'Inside Function'; }"
alias foo

foo='foo(){ echo '\''Inside Function'\''; }'

Sehen Sie, was es dort mit den Zitaten macht? Die genaue Ausgabe hängt von der Shell ab. Im Allgemeinen können Sie sich jedoch darauf verlassen, dass eine Shell ihre aliasDefinitionen auf eine Weise evalmeldet, die für die erneute Eingabe in dieselbe Shell sicher ist . Das Mischen und Anpassen von Quell- / Ziel-Shells kann jedoch in einigen Fällen zweifelhaft sein.

Um einen Grund für diese Vorsicht aufzuzeigen:

ksh -c 'alias foo="
    foo(){
        echo \"Inside Function\"
    }"
    alias foo'

foo=$'\nfoo(){\n\techo "Inside Function"\n}'

Beachten Sie auch, dass der aliasNamespace einen von drei unabhängigen Namespaces darstellt, von denen Sie allgemein erwarten können, dass sie in praktisch jeder modernen Shell vollständig ausgearbeitet sind. In der Regel bieten Shells diese drei Namespaces:

  • Variablen: name=valueaußerhalb von Listen

    • Diese können auch mit einigen eingebauten Funktionen wie definiert werden export, set, readonlyin einigen Fällen.
  • Funktionen: name() compound_command <>any_redirectsin Befehlsposition

    • Die Schale ist angegeben nicht zu erweitern oder einen beliebigen Teil der Funktionsdefinition zu interpretieren es in dem Befehl zur Definitionszeit finden könnte.
      • Dieses Verbot enthält jedoch keine Aliase, da diese während des Analyseprozesses des Lesens eines Shell-Befehls erweitert werden und aliasWerte, wenn aliassie im richtigen Kontext gefunden werden, beim Auffinden in den Funktionsdefinitionsbefehl erweitert werden.
    • Wie bereits erwähnt, speichert die Shell den analysierten Wert des Definitionsbefehls als statische Zeichenfolge in ihrem Speicher und führt alle in der Zeichenfolge gefundenen Erweiterungen und Umleitungen nur aus, wenn der Funktionsname später nach dem Parsen wie aufgerufen gefunden wird.
  • Aliase: alias name=valuein Kommandoposition

    • Wie Funktionen werden aliasDefinitionen interpretiert, wenn die Definitionsnamen später an der Befehlsposition gefunden werden.
    • Im Gegensatz zu Funktionen werden aliasgültige Shell-Erweiterungen, die darin enthalten sind, zum definierten Zeitpunkt ebenfalls interpretiert , da ihre Definitionen Argumente für den Befehl sind. Und so werden aliasDefinitionen immer zweimal interpretiert.
    • Im Gegensatz zu Funktionen werden aliasDefinitionen zur Definitionszeit überhaupt nicht analysiert, sondern es ist der Parser der Shell, der ihre Werte vor dem Parsen in eine Befehlszeichenfolge erweitert, wenn sie gefunden werden.

Sie haben wahrscheinlich bemerkt, was meiner Meinung nach der Hauptunterschied zwischen einer aliasoder einer Funktion oben ist, die der Expansionspunkt der Shell für jede Funktion ist. Die Unterschiede können ihre Kombination tatsächlich sehr nützlich machen.

alias foo='
    foo(){
        printf "Inside Function: %s\n" "$@"
        unset -f foo
    };  foo "$@" '

Dies ist ein Alias, der eine Funktion definiert, die sich beim Aufruf selbst aufhebt und daher einen Namespace durch die Anwendung eines anderen beeinflusst. Es ruft dann die Funktion auf. Das Beispiel ist naiv; In einem regulären Kontext ist es nicht sehr nützlich, aber das folgende Beispiel zeigt möglicherweise eine andere eingeschränkte Nützlichkeit, die es in anderen Kontexten bietet:

sh -c "
    alias $(alias foo)
    foo \'
" -- \; \' \" \\

Inside Function: ;
Inside Function: '
Inside Function: "
Inside Function: \
Inside Function: '

Sie sollten immer einen aliasNamen in einer anderen Eingabezeile als der aufrufen, in der sich die Definition befindet. Dies hängt wiederum mit der Analysereihenfolge des Befehls zusammen. In Kombination können Aliase und Funktionen zusammen verwendet werden, um Informationen und Parameterarrays portabel, sicher und effektiv in und aus Subshelled-Kontexten zu verschieben.


Fast ohne Beziehung, aber hier ist noch eine lustige:

alias mlinesh='"${SHELL:-sh}" <<"" '

Führen Sie dies an einer interaktiven Eingabeaufforderung aus, und alle Argumente, die Sie in derselben Ausführungszeile wie die Zeile, in der Sie es aufrufen, übergeben, werden als Argumente für eine Subshell interpretiert. Alle Zeilen danach bis zur ersten auftretenden Leerzeile werden jedoch von der aktuellen Shell eingelesen, um als Standard an die Subshell zu übergeben, die sie zum Aufrufen vorbereitet, und keine dieser Zeilen wird in irgendeiner Weise interpretiert.

$ mlinesh -c '. /dev/fd/0; foo'
> foo(){ echo 'Inside Function'; } 
> 

Inside Function

... aber nur ...

$ mlinesh
> foo(){ echo 'Inside Function'; }
> foo
> 

...ist einfacher...

mikeserv
quelle