Comment définir et charger votre propre fonction shell dans zsh

55

J'ai du mal à définir et à exécuter mes propres fonctions de shell dans zsh. J'ai suivi les instructions de la documentation officielle et essayé d'abord avec un exemple simple, mais je n'ai pas réussi à le faire fonctionner.

J'ai un dossier:

~/.my_zsh_functions

Dans ce dossier, j'ai un fichier appelé functions_1avec rwxdes autorisations utilisateur. Dans ce fichier, j'ai défini la fonction shell suivante:

my_function () {
echo "Hello world";
}

J'ai défini FPATHd'inclure le chemin d'accès au dossier ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Je peux confirmer que le dossier .my_zsh_functionsest dans le chemin des fonctions avec echo $FPATHouecho $fpath

Cependant, si j’essaie ensuite ce qui suit à partir du shell:

> autoload my_function
> my_function

Je reçois:

zsh: my_test_function: fichier de définition de fonction introuvable

Dois-je faire autre chose pour pouvoir appeler my_function?

Mise à jour:

Les réponses suggèrent jusqu'ici de rechercher le fichier avec les fonctions zsh. Cela a du sens, mais je suis un peu confus. Zsh ne devrait-il pas savoir où se trouvent ces fichiers FPATH? Quel est le but autoloadalors?

Amelio Vazquez-Reina
la source
Assurez-vous que le $ ZDOTDIR est correctement défini. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
La valeur de $ ZDOTDIR n'est pas liée à ce problème. La variable définit où zsh recherche les fichiers de configuration d'un utilisateur. S'il n'est pas défini, $ HOME est utilisé à la place, ce qui est la bonne valeur pour presque tout le monde.
Frank Terbeck

Réponses:

96

Dans zsh, le chemin de recherche de la fonction ($ fpath) définit un ensemble de répertoires contenant des fichiers pouvant être marqués pour être chargés automatiquement lorsque la fonction qu’ils contiennent est requise pour la première fois.

Zsh a deux modes de chargement automatique des fichiers: le mode natif de Zsh et un autre mode qui ressemble au chargement automatique de ksh. Ce dernier est actif si l'option KSH_AUTOLOAD est définie. Le mode natif de Zsh est le mode par défaut et je ne discuterai pas de l'inverse (voir "man zshmisc" et "man zshoptions" pour plus de détails sur le chargement automatique de style ksh).

D'accord. Supposons que vous ayez un répertoire `~ / .zfunc 'et que vous souhaitiez qu'il fasse partie du chemin de recherche de la fonction, procédez comme suit:

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

Cela ajoute votre répertoire privé au début du chemin de recherche. Cela est important si vous souhaitez remplacer les fonctions de l'installation de zsh par les vôtres (par exemple, lorsque vous souhaitez utiliser une fonction de complétion mise à jour telle que `_git 'du référentiel CVS de zsh avec une version plus ancienne du shell).

Il est également intéressant de noter que les répertoires de `$ fpath 'ne sont pas recherchés de manière récursive. Si vous souhaitez que votre répertoire privé fasse l'objet d'une recherche récursive, vous devrez vous en occuper vous-même, comme ceci (le fragment de code suivant nécessite la définition de l'option `EXTENDED_GLOB '):

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

Cela peut sembler mystérieux à l’œil nu, mais il ajoute en fait tous les répertoires situés sous ~ ~. arborescence des fonctions du CVS de zsh dans votre chemin de recherche privé).

Supposons que vous ayez un fichier `~ / .zfunc / hello 'contenant la ligne suivante:

printf 'Hello world.\n'

Il ne vous reste plus qu'à marquer la fonction à charger automatiquement dès sa première référence:

autoload -Uz hello

"De quoi parle le -Uz?", Vous demandez? Eh bien, c’est juste un ensemble d’options qui feront que le «chargement automatique» fera le bon choix, quelles que soient les options définies. Le «U» désactive le développement des alias pendant le chargement de la fonction et le «z» force le chargement automatique dans le style zsh même si «KSH_AUTOLOAD» est défini pour une raison quelconque.

Une fois cela fait, vous pouvez utiliser votre nouvelle fonction `hello ':

zsh% bonjour
Bonjour le monde.

Un mot sur la recherche de ces fichiers: c’est faux . Si vous aviez source ce fichier `~ / .zfunc / hello ', il n’aurait qu’imprimer" Hello world ". une fois que. Rien de plus. Aucune fonction ne sera définie. De plus, l’idée est de ne charger le code de la fonction que lorsque cela est nécessaire . Après l'appel `autoload ', la définition de la fonction n'est pas lue. La fonction est juste marquée pour être auto-chargée plus tard si nécessaire.

Et enfin, une note sur $ FPATH et $ fpath: Zsh les maintient en tant que paramètres liés. Le paramètre minuscule est un tableau. La version majuscule est une chaîne scalaire, qui contient les entrées du tableau lié joint par des points entre deux entrées. Ceci est fait car la gestion d'une liste de scalaires est beaucoup plus naturelle à l'aide de tableaux, tout en maintenant la compatibilité avec les versions antérieures pour le code qui utilise le paramètre scalar. Si vous choisissez d'utiliser $ FPATH (le scalaire), vous devez faire attention:

FPATH=~/.zfunc:$FPATH

fonctionnera, tandis que ce qui suit ne fonctionnera pas:

FPATH="~/.zfunc:$FPATH"

La raison en est que le développement de tilde n’est pas effectué entre guillemets doubles. C'est probablement la source de vos problèmes. Si echo $FPATHimprime un tilde et non un chemin développé, cela ne fonctionnera pas. Pour être sûr, j'utiliserais $ HOME au lieu d'un tilde comme celui-ci:

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

Cela dit, je préférerais de beaucoup utiliser le paramètre array comme je l’ai fait en haut de cette explication.

Vous ne devriez pas non plus exporter le paramètre $ FPATH. Il n’est nécessaire que par le processus shell actuel et non par l’un de ses enfants.

Mise à jour

En ce qui concerne le contenu des fichiers dans `$ fpath ':

Avec le chargement automatique de style zsh, le contenu d'un fichier est le corps de la fonction qu'il définit. Ainsi, un fichier nommé "hello" contenant une ligne echo "Hello world."définit complètement une fonction appelée "hello". Vous êtes libre de hello () { ... }contourner le code, mais ce serait superflu.

L'affirmation selon laquelle un fichier ne peut contenir qu'une seule fonction n'est cependant pas tout à fait correcte.

Surtout si vous regardez certaines fonctions du système de complétion basé sur les fonctions (compsys), vous vous rendrez vite compte qu'il s'agit d'une idée fausse. Vous êtes libre de définir des fonctions supplémentaires dans un fichier de fonctions. Vous êtes également libre de faire toute sorte d’initialisation que vous devrez peut-être faire lors du premier appel de la fonction. Cependant, lorsque vous le ferez, vous définissez toujours une fonction nommée comme le fichier dans le fichier et appelez cette fonction à la fin du fichier pour qu'il soit exécuté la première fois que la fonction est référencée.

Si - avec les sous-fonctions - vous ne définissez pas une fonction nommée comme le fichier dans le fichier, vous obtiendrez des définitions de fonctions contenant cette fonction (à savoir celles des sous-fonctions du fichier). Vous définiriez effectivement toutes vos sous-fonctions chaque fois que vous appelez la fonction nommée comme le fichier. Normalement, ce n'est pas ce que vous voulez, vous redéfinissez donc une fonction nommée comme le fichier dans le fichier.

Je vais inclure un court squelette, qui vous donnera une idée de la façon dont cela fonctionne:

# 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 "$@"

Si vous utilisiez cet exemple stupide, la première exécution ressemblerait à ceci:

zsh% bonjour
initialisation ...
Bonjour le monde.

Et les appels consécutifs ressembleront à ceci:

zsh% bonjour
Bonjour le monde.

J'espère que cela clarifie les choses.

(L'un des exemples les plus complexes du monde réel qui utilise toutes ces astuces est la fonction ` _tmux ' déjà mentionnée du système d'achèvement basé sur la fonction de zsh.)

Frank Terbeck
la source
Merci Frank! J'ai lu dans les autres réponses que je ne peux définir qu'une fonction par fichier, n'est-ce pas? J'ai remarqué que vous n'avez pas utilisé la syntaxe my_function () { }dans votre Hello worldexemple. Si la syntaxe n'est pas nécessaire, quand serait-il utile de l'utiliser?
Amelio Vazquez-Reina
1
J'ai étendu la réponse initiale pour répondre à ces questions également.
Frank Terbeck
“Vous définirez toujours dans la fonction nommée comme le fichier dans le fichier et appelerez cette fonction à la fin du fichier”: pourquoi?
Hibou57
Hibou57: (À part: c'est une faute de frappe ici, il devrait être "définir une fonction", c'est corrigé maintenant.) J'ai pensé que c'était clair, lorsque vous prenez en compte le fragment de code qui suit. Quoi qu'il en soit, j'ai ajouté un paragraphe qui énonce la raison un peu plus littéralement.
Frank Terbeck
Voici plus d'informations de votre site, merci.
Timo
5

Le nom du fichier dans un répertoire nommé par un fpathélément doit correspondre au nom de la fonction autochargeable qu’il définit.

Votre fonction est nommée my_functionet ~/.my_zsh_functionscorrespond au répertoire prévu dans votre fpath; la définition de my_functiondevrait donc figurer dans le fichier ~/.my_zsh_functions/my_function.

Le pluriel dans votre nom de fichier proposé ( functions_1) indique que vous aviez l'intention de placer plusieurs fonctions dans le fichier. Ce n'est pas comme ça fpathet le chargement automatique fonctionne. Vous devriez avoir une définition de fonction par fichier.

Chris Johnsen
la source
2

donner source ~/.my_zsh_functions/functions1dans le terminal et évaluer my_function, maintenant vous pourrez appeler la fonction

harish.venkat
la source
2
Merci, mais quel est le rôle de FPATHet autoloadensuite? Pourquoi ai-je également besoin de source le fichier? Voir ma question mise à jour.
Amelio Vazquez-Reina
1

Vous pouvez "charger" un fichier avec toutes vos fonctions dans votre $ ZDOTDIR / .zshrc comme ceci:

source $ZDOTDIR/functions_file

Ou peut utiliser un point "." au lieu de "source".

Ramonovski
la source
1
Merci, mais quel est le rôle de FPATHet autoloadensuite? Pourquoi ai-je également besoin de source le fichier? Voir ma question mise à jour.
Amelio Vazquez-Reina
0

L’approvisionnement n’est certainement pas la bonne approche, car ce que vous semblez vouloir, c’est avoir des fonctions initialisées paresseuses. C'est pour ça autoload. Voici comment vous accomplissez ce que vous recherchez.

Dans votre ~/.my_zsh_functions, vous dites que vous voulez mettre une fonction appelée my_functionechos "hello world". Mais vous l'enroulez dans un appel de fonction, ce qui n'est pas le cas. Au lieu de cela, vous devez créer un fichier appelé ~/.my_zsh_functions/my_function. Dans ce document, mettez simplement echo "Hello world", pas dans un wrapper de fonction. Vous pouvez également faire quelque chose comme ceci si vous préférez vraiment avoir le wrapper.

# ~/.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

Ensuite, dans votre .zshrcfichier, ajoutez ce qui suit:

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

Lorsque vous chargez un nouveau shell ZSH, tapez which my_function. Vous devriez voir ceci:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH vient d'écraser ma_fonction pour vous autoload -X. Maintenant, lancez-vous my_functionmais tapez juste my_function. Vous devriez voir Hello worldimprimer, et maintenant quand vous courez, which my_functionvous devriez voir la fonction remplie comme ceci:

my_function () {
    echo "Hello world"
}

Maintenant, la vraie magie vient lorsque vous configurez votre ~/.my_zsh_functionsdossier entier pour travailler avec autoload. Si vous voulez que tous les fichiers que vous déposez dans ce dossier fonctionnent comme ceci, changez ce que vous mettez dans votre .zshrcen quelque chose comme ceci:

# 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
la source