web-dev-qa-db-fra.com

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

J'ai du mal à définir et à exécuter mes propres fonctions Shell dans zsh. J'ai suivi les instructions sur la documentation officielle et j'ai d'abord essayé 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_1 avec rwx autorisations utilisateur. Dans ce fichier, la fonction Shell suivante est définie:

my_function () {
echo "Hello world";
}

J'ai défini FPATH pour 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_functions est dans le chemin des fonctions avec echo $FPATH ou echo $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

Y a-t-il autre chose que je dois faire pour pouvoir appeler my_function?

Mise à jour:

Jusqu'à présent, les réponses suggèrent de sourcer 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 avec FPATH? Quel est le but de autoload alors?

60

Dans zsh, le chemin de recherche de fonction ($ fpath) définit un ensemble de répertoires, qui contiennent des fichiers qui peuvent être marqués pour être chargés automatiquement lorsque la fonction qu'ils contiennent est nécessaire 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'autre manière ici (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 fonction, procédez comme suit:

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

Cela ajoute votre répertoire privé à l'avant du chemin de recherche. Cela est important si vous souhaitez remplacer les fonctions de l'installation de zsh par les vôtres (comme lorsque vous souhaitez utiliser une fonction d'achèvement mise à jour telle que `_git 'du référentiel CVS de zsh avec une ancienne version installée du shell).

Il convient également 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é soit recherché de manière récursive, vous devrez vous en occuper vous-même, comme ceci (l'extrait de code suivant nécessite la définition de l'option `EXTENDED_GLOB '):

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

Cela peut sembler cryptique à l'œil non averti, mais il ajoute vraiment tous les répertoires sous `~/.zfunc 'à` $ fpath', tout en ignorant les répertoires appelés "CVS" (ce qui est utile, si vous prévoyez de vérifier un tout fonction de CVS de zsh dans votre chemin de recherche privé).

Supposons que vous ayez un fichier `~/.zfunc/hello 'qui contient 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?", Demandez-vous? Eh bien, ce n'est qu'un ensemble d'options qui feront que le chargement automatique fera la bonne chose, quelles que soient les options définies autrement. Le "U" désactive l'expansion d'alias pendant le chargement de la fonction et le "z" force le chargement automatique de style zsh même si "KSH_AUTOLOAD" est défini pour une raison quelconque.

Après avoir pris soin de cela, vous pouvez utiliser votre nouvelle fonction "bonjour":

zsh% bonjour 
 Bonjour tout le monde.

Un mot sur l'approvisionnement de ces fichiers: c'est juste faux . Si vous sourcez ce fichier `~/.zfunc/hello ', il affichera simplement" Hello world ". une fois que. Rien de plus. Aucune fonction ne sera définie. Et d'ailleurs, l'idée est de ne charger le code de la fonction que lorsqu'elle est requise . Après l'appel `autoload ', la définition de la fonction n'est pas lue. La fonction est juste marquée pour être chargée automatiquement plus tard si nécessaire.

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

FPATH=~/.zfunc:$FPATH

fonctionnera, tandis que les éléments suivants ne fonctionneront pas:

FPATH="~/.zfunc:$FPATH"

La raison en est que l'expansion du tilde n'est pas effectuée entre guillemets doubles. C'est probablement la source de vos problèmes. Si echo $FPATH Imprime 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 étant dit, je préfère de loin utiliser le paramètre tableau comme je l'ai fait en haut de cette explication.

Vous ne devez pas non plus exporter le paramètre $ FPATH. Il n'est nécessaire que par le processus Shell actuel et par aucun de ses enfants.

Mise à jour

Concernant 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é "bonjour" contenant une ligne echo "Hello world." Définit complètement une fonction appelée "bonjour". Vous êtes libre de mettre hello () { ... } autour du code, mais ce serait superflu.

L'affirmation selon laquelle un fichier ne peut contenir qu'une seule fonction n'est pas entièrement correcte, cependant.

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

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

Je vais inclure un court squelette, qui vous donnera une idée de comment 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 exécutez cet exemple stupide, la première exécution ressemblerait à ceci:

zsh% bonjour 
 initialisation ... 
 Bonjour tout le monde.

Et les appels consécutifs ressembleront à ceci:

zsh% bonjour 
 Bonjour tout 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 de complétion basé sur les fonctions de zsh.)

103
Frank Terbeck

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

Votre fonction s'appelle my_function et ~/.my_zsh_functions est le répertoire prévu dans votre fpath, donc la définition de my_function devrait être dans le fichier ~/.my_zsh_functions/my_function.

Le pluriel du nom de fichier proposé (functions_1) indique que vous envisagiez de mettre plusieurs fonctions dans le fichier. Ce n'est pas ainsi que fpath et le chargement automatique fonctionnent. Vous devez avoir une définition de fonction par fichier.

6
Chris Johnsen

donner source ~/.my_zsh_functions/functions1 dans le terminal et l'évaluation my_function, vous pourrez maintenant appeler la fonction

2
harish.venkat

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".

1
ramonovski

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 à cela que sert autoload. Voici comment vous accomplissez ce que vous recherchez.

Dans ton ~/.my_zsh_functions, vous dites que vous voulez mettre une fonction appelée my_function qui fait écho à "bonjour le monde". Mais, vous l'encapsulez dans un appel de fonction, ce qui n'est pas ainsi que cela fonctionne. 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 ça 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 .zshrc fichier, 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 de supprimer ma_fonction pour vous avec autoload -X. Maintenant, exécutez my_function mais juste en tapant my_function. Tu devrais voir Hello world imprimer, et maintenant lorsque vous exécutez which my_function vous devriez voir la fonction remplie comme ceci:

my_function () {
    echo "Hello world"
}

Maintenant, la vraie magie vient quand vous configurez l'ensemble de votre ~/.my_zsh_functions dossier avec lequel travailler autoload. Si vous voulez que chaque fichier que vous déposez dans ce dossier fonctionne comme ceci, changez ce que vous mettez dans votre .zshrc à 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)
0
mattmc3