So definieren und laden Sie Ihre eigene Shell-Funktion in zsh

54

Es fällt mir schwer, meine eigenen Shell-Funktionen in zsh zu definieren und auszuführen. Ich befolgte die Anweisungen in der offiziellen Dokumentation und versuchte es zunächst mit einem einfachen Beispiel, brachte es aber nicht zum Laufen.

Ich habe einen Ordner:

~/.my_zsh_functions

In diesem Ordner habe ich eine Datei functions_1mit rwxBenutzerberechtigungen aufgerufen . In dieser Datei habe ich folgende Shell-Funktion definiert:

my_function () {
echo "Hello world";
}

Ich definierte FPATH, um den Pfad zum Ordner einzuschließen ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Ich kann .my_zsh_functionsmit echo $FPATHoder bestätigen, dass sich der Ordner im Funktionspfad befindetecho $fpath

Wenn ich dann aber folgendes aus der Shell probiere:

> autoload my_function
> my_function

Ich bekomme:

zsh: my_test_function: Funktionsdefinitionsdatei nicht gefunden

Muss ich noch etwas tun, um anrufen zu können my_function?

Aktualisieren:

Die bisherigen Antworten legen nahe, die Datei mit den zsh-Funktionen zu beschaffen. Das macht Sinn, aber ich bin ein bisschen verwirrt. Sollte zsh nicht wissen, wo sich diese Dateien befinden FPATH? Was ist der Zweck von autoloaddann?

Amelio Vazquez-Reina
quelle
Stellen Sie sicher, dass Sie $ ZDOTDIR richtig definiert haben. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
Der Wert von $ ZDOTDIR hängt nicht mit diesem Problem zusammen. Die Variable definiert, wo zsh nach Konfigurationsdateien eines Benutzers sucht. Wenn es nicht gesetzt ist, wird stattdessen $ HOME verwendet, was für fast alle der richtige Wert ist.
Frank Terbeck

Antworten:

95

In zsh definiert der Funktionssuchpfad ($ fpath) eine Reihe von Verzeichnissen, die Dateien enthalten, die zum automatischen Laden markiert werden können, wenn die darin enthaltene Funktion zum ersten Mal benötigt wird.

Zsh bietet zwei Modi zum automatischen Laden von Dateien: den systemeigenen Modus von Zsh und einen anderen Modus, der dem automatischen Laden von ksh ähnelt. Letzteres ist aktiv, wenn die Option KSH_AUTOLOAD gesetzt ist. Der native Modus von Zsh ist die Standardeinstellung, und ich werde hier nicht auf die andere Weise eingehen (siehe "man zshmisc" und "man zshoptions" für Details zum automatischen Laden im ksh-Stil).

Okay. Angenommen, Sie haben ein Verzeichnis "~ / .zfunc" und möchten, dass es Teil des Funktionssuchpfads ist. Gehen Sie dazu folgendermaßen vor:

fpath=( ~/.zfunc "${fpath[@]}" )

Das fügt Ihr privates Verzeichnis an der Spitze des Suchpfads hinzu. Dies ist wichtig, wenn Sie Funktionen aus der Installation von zsh mit Ihren eigenen überschreiben möchten (z. B. wenn Sie eine aktualisierte Vervollständigungsfunktion wie _git aus dem CVS-Repository von zsh mit einer älteren installierten Version der Shell verwenden möchten).

Es ist auch erwähnenswert, dass die Verzeichnisse von $ fpath nicht rekursiv durchsucht werden. Wenn Sie möchten, dass Ihr privates Verzeichnis rekursiv durchsucht wird, müssen Sie sich selbst darum kümmern (für das folgende Snippet muss die Option EXTENDED_GLOB gesetzt sein):

fpath=(
    ~/.zfuncs
    ~/.zfuncs/**/*~*/(CVS)#(/N)
    "${fpath[@]}"
)

Es sieht für das ungeübte Auge vielleicht kryptisch aus, fügt aber wirklich nur alle Verzeichnisse unterhalb von ~ / .zfunc zu $ ​​fpath hinzu, während Verzeichnisse mit dem Namen "CVS" ignoriert werden (was nützlich ist, wenn Sie vorhaben, ein Ganzes auszuchecken Funktionsbaum aus dem CVS von zsh in Ihren privaten Suchpfad).

Nehmen wir an, Sie haben eine Datei "~ / .zfunc / hello", die die folgende Zeile enthält:

printf 'Hello world.\n'

Alles, was Sie jetzt tun müssen, ist , die Funktion zu markieren, die bei ihrer ersten Referenz automatisch geladen werden soll:

autoload -Uz hello

"Worum geht es in der -Uz?", Fragst du? Nun, das ist nur eine Reihe von Optionen, die dazu führen, dass "Autoload" das Richtige tut, unabhängig davon, welche Optionen ansonsten eingestellt werden. Das "U" deaktiviert die Alias-Erweiterung, während die Funktion geladen wird, und das "z" erzwingt das automatische Laden im Stil von "zsh", auch wenn "KSH_AUTOLOAD" aus irgendeinem Grund festgelegt ist.

Nachdem dies erledigt wurde, können Sie Ihre neue "Hallo" -Funktion verwenden:

zsh% hallo
Hallo Welt.

Ein Wort zur Beschaffung dieser Dateien: Das ist einfach falsch . Wenn Sie diese `~ / .zfunc / hello'-Datei als Quelle verwenden, wird nur" Hallo Welt "gedruckt. Einmal. Nichts mehr. Es wird keine Funktion definiert. Außerdem besteht die Idee darin, den Funktionscode nur dann zu laden, wenn er benötigt wird . Nach dem Aufruf von `autoload 'wird die Definition der Funktion nicht gelesen. Die Funktion ist nur markiert, um später bei Bedarf automatisch geladen zu werden.

Zum Schluss noch ein Hinweis zu $ ​​FPATH und $ fpath: Zsh behält diese als verknüpfte Parameter bei. Der Parameter in Kleinbuchstaben ist ein Array. Die Großbuchstabenversion ist ein String-Skalar, der die Einträge aus dem verknüpften Array enthält, die durch Doppelpunkte zwischen den Einträgen verbunden sind. Dies geschieht, weil der Umgang mit einer Liste von Skalaren mit Arrays viel natürlicher ist und gleichzeitig die Abwärtskompatibilität für Code aufrechterhalten wird, der den Parameter scalar verwendet. Wenn Sie sich für die Verwendung von $ FPATH (dem skalaren) entscheiden, müssen Sie vorsichtig sein:

FPATH=~/.zfunc:$FPATH

wird funktionieren, während die folgenden nicht:

FPATH="~/.zfunc:$FPATH"

Der Grund dafür ist, dass die Tilde-Erweiterung nicht in doppelten Anführungszeichen erfolgt. Dies ist wahrscheinlich die Ursache Ihrer Probleme. Wenn echo $FPATHeine Tilde und kein erweiterter Pfad gedruckt wird, funktioniert dies nicht. Aus Sicherheitsgründen würde ich $ HOME anstelle einer Tilde wie folgt verwenden:

FPATH="$HOME/.zfunc:$FPATH"

Davon abgesehen würde ich den Array-Parameter viel lieber verwenden, wie ich es oben in dieser Erklärung getan habe.

Sie sollten den Parameter $ FPATH auch nicht exportieren. Es wird nur vom aktuellen Shell-Prozess und nicht von einem seiner untergeordneten Elemente benötigt.

Aktualisieren

Bezüglich des Inhalts von Dateien in "$ fpath":

Beim automatischen Laden im Stil von zsh ist der Inhalt einer Datei der Hauptteil der von ihr definierten Funktion. Somit definiert eine Datei mit dem Namen "hallo", die eine Zeile enthält, echo "Hello world."vollständig eine Funktion mit dem Namen "hallo". Es steht Ihnen frei, hello () { ... }den Code zu umgehen, aber das wäre überflüssig.

Die Behauptung, dass eine Datei nur eine Funktion enthalten darf, ist jedoch nicht ganz richtig.

Insbesondere wenn Sie sich einige Funktionen des funktionsbasierten Vervollständigungssystems (compsys) ansehen, werden Sie schnell feststellen, dass dies ein Missverständnis ist. Sie können zusätzliche Funktionen in einer Funktionsdatei definieren. Sie können auch jede Art von Initialisierung durchführen, die Sie möglicherweise beim ersten Aufruf der Funktion durchführen müssen. Wenn Sie dies jedoch tun, definieren Sie immer eine Funktion, die wie die Datei in der Datei benannt ist, und rufen diese Funktion am Ende der Datei auf, damit sie beim ersten Aufrufen der Funktion ausgeführt wird.

Wenn Sie bei Unterfunktionen keine Funktion definiert haben, die wie die Datei in der Datei benannt ist, enthält diese Funktion Funktionsdefinitionen (dh die Unterfunktionen in der Datei). Sie würden effektiv alle Ihre Unterfunktionen jedes Mal definieren, wenn Sie die Funktion aufrufen, die wie die Datei benannt ist. Normalerweise ist das nicht das, was Sie wollen, also würden Sie eine Funktion neu definieren, die wie die Datei in der Datei benannt ist.

Ich werde ein kurzes Skelett beifügen, das Ihnen eine Vorstellung davon gibt, wie das funktioniert:

# Let's again assume that these are the contents of a file called "hello".

# You may run arbitrary code in here, that will run the first time the
# function is referenced. Commonly, that is initialisation code. For example
# the `_tmux' completion function does exactly that.
echo initialising...

# You may also define additional functions in here. Note, that these
# functions are visible in global scope, so it is paramount to take
# care when you're naming these so you do not shadow existing commands or
# redefine existing functions.
hello_helper_one () {
    printf 'Hello'
}

hello_helper_two () {
    printf 'world.'
}

# Now you should redefine the "hello" function (which currently contains
# all the code from the file) to something that covers its actual
# functionality. After that, the two helper functions along with the core
# function will be defined and visible in global scope.
hello () {
    printf '%s %s\n' "$(hello_helper_one)" "$(hello_helper_two)"
}

# Finally run the redefined function with the same arguments as the current
# run. If this is left out, the functionality implemented by the newly
# defined "hello" function is not executed upon its first call. So:
hello "$@"

Wenn Sie dieses alberne Beispiel ausführen würden, würde der erste Lauf folgendermaßen aussehen:

zsh% hallo
Initialisierung ...
Hallo Welt.

Aufeinanderfolgende Anrufe sehen dann so aus:

zsh% hallo
Hallo Welt.

Ich hoffe das klärt die Dinge auf.

(Eines der komplexeren realen Beispiele, das all diese Tricks verwendet, ist die bereits erwähnte Funktion " _tmux " aus dem funktionsbasierten Vervollständigungssystem von zsh.)

Frank Terbeck
quelle
Danke Frank! Ich habe in den anderen Antworten gelesen, dass ich nur eine Funktion pro Datei definieren kann, stimmt das? Mir ist aufgefallen, dass Sie my_function () { }in Ihrem Hello worldBeispiel nicht die Syntax verwendet haben . Wenn die Syntax nicht benötigt wird, wann ist sie sinnvoll?
Amelio Vazquez-Reina
1
Ich habe die ursprüngliche Antwort erweitert, um auch diese Fragen zu beantworten.
Frank Terbeck
"Sie werden immer in der Funktion definieren, die wie die Datei in der Datei benannt ist, und diese Funktion am Ende der Datei aufrufen": warum so?
Hibou57
Hibou57: (Nebenbei: Das ist ein Tippfehler, sollte "eine Funktion definieren" sein, der jetzt behoben ist.) Ich dachte, das wäre klar, wenn man das folgende Code-Snippet in Betracht zieht. Wie auch immer, ich habe einen Absatz hinzugefügt, der den Grund ein bisschen wörtlicher ausdrückt.
Frank Terbeck
Hier finden Sie weitere Informationen von Ihrer Website, danke.
Timo
5

Der Name der Datei in einem Verzeichnis, das von einem fpathElement benannt wird , muss mit dem Namen der von ihm definierten automatisch ladbaren Funktion übereinstimmen.

Ihre Funktion heißt my_functionund ~/.my_zsh_functionsist das beabsichtigte Verzeichnis in Ihrer fpath, daher sollte die Definition von my_functionin der Datei sein ~/.my_zsh_functions/my_function.

Der Plural in Ihrem vorgeschlagenen Dateinamen ( functions_1) zeigt an, dass Sie mehrere Funktionen in die Datei einfügen wollten. Dies ist nicht wie fpathund Autoloading funktioniert. Sie sollten eine Funktionsdefinition pro Datei haben.

Chris Johnsen
quelle
2

Geben Sie source ~/.my_zsh_functions/functions1das Terminal ein und bewerten Sie my_function, jetzt können Sie die Funktion aufrufen

harish.venkat
quelle
2
Danke, aber was ist die Rolle von FPATHund autoloaddann? Warum muss ich die Datei auch als Quelle angeben? Siehe meine aktualisierte Frage.
Amelio Vazquez-Reina
1

Sie können eine Datei mit all Ihren Funktionen in Ihrem $ ZDOTDIR / .zshrc wie folgt "laden":

source $ZDOTDIR/functions_file

Oder kann einen Punkt "." anstelle von "Quelle".

ramonovski
quelle
1
Danke, aber was ist die Rolle von FPATHund autoloaddann? Warum muss ich die Datei auch als Quelle angeben? Siehe meine aktualisierte Frage.
Amelio Vazquez-Reina
0

Sourcing ist definitiv nicht der richtige Ansatz, da Sie scheinbar nur faule, initialisierte Funktionen benötigen. Dafür autoloadist es da. So erreichen Sie, wonach Sie suchen.

In Ihrem ~/.my_zsh_functions, Sie sagen , Sie eine Funktion namens setzen wollen , my_functiondass echos „Hallo Welt“. Aber Sie binden es in einen Funktionsaufruf ein, und so funktioniert das nicht. Stattdessen müssen Sie eine Datei namens erstellen ~/.my_zsh_functions/my_function. Einfach einfügen echo "Hello world", nicht in einen Funktionswrapper. Sie können auch so etwas tun, wenn Sie den Wrapper wirklich bevorzugen.

# ~/.my_zsh_functions/my_function
__my_function () {
    echo "Hello world";
}
# you have to call __my_function
# if this is how you choose to do it
__my_function

Fügen Sie als Nächstes in Ihrer .zshrcDatei Folgendes hinzu:

fpath=(~/.my_zsh_functions $fpath);
autoload -U ~/.my_zsh_functions/my_function

Wenn Sie eine neue ZSH-Shell laden, geben Sie Folgendes ein which my_function. Das solltest du sehen:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH hat gerade meine_Funktion für Sie gestoppt autoload -X. Jetzt renn my_functiondoch einfach nur tippen my_function. Sie sollten den Hello worldAusdruck sehen, und jetzt, wenn Sie ausführen which my_function, sollten Sie die Funktion so ausgefüllt sehen:

my_function () {
    echo "Hello world"
}

Jetzt kommt die wahre Magie, wenn Sie Ihren gesamten ~/.my_zsh_functionsOrdner für die Arbeit einrichten autoload. Wenn Sie möchten, dass jede Datei, die Sie in diesem Ordner ablegen, so funktioniert, ändern Sie das, was Sie in Ihrem Ordner ablegen, in Folgendes .zshrc:

# add ~/.my_zsh_functions to fpath, and then lazy autoload
# every file in there as a function
fpath=(~/.my_zsh_functions $fpath);
autoload -U fpath[1]/*(.:t)
mattmc3
quelle