Come definire e caricare la propria funzione di shell in zsh


55

Sto avendo difficoltà a definire ed eseguire le mie funzioni di shell in zsh. Ho seguito le istruzioni sulla documentazione ufficiale e ho provato prima con un semplice esempio, ma non sono riuscito a farlo funzionare.

Ho una cartella:

~/.my_zsh_functions

In questa cartella ho un file chiamato functions_1con rwxautorizzazioni utente. In questo file ho definito la seguente funzione shell:

my_function () {
echo "Hello world";
}

Ho definito FPATHper includere il percorso della cartella ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Posso confermare che la cartella si .my_zsh_functionstrova nel percorso delle funzioni con echo $FPATHoecho $fpath

Tuttavia, se provo quindi quanto segue dalla shell:

> autoload my_function
> my_function

Ottengo:

zsh: my_test_function: file di definizione della funzione non trovato

C'è qualcos'altro che devo fare per poter chiamare my_function?

Aggiornare:

Le risposte finora suggeriscono di approvvigionare il file con le funzioni zsh. Questo ha senso, ma sono un po 'confuso. Zsh non dovrebbe sapere dove si trovano questi file FPATH? Qual è lo scopo di autoloadallora?


Assicurati di avere $ ZDOTDIR correttamente definito. zsh.sourceforge.net/Intro/intro_3.html
ramonovski

2
Il valore di $ ZDOTDIR non è correlato a questo problema. La variabile definisce dove zsh sta cercando i file di configurazione di un utente. Se non è impostato, viene utilizzato $ HOME, che è il valore giusto per quasi tutti.
Frank Terbeck,

Risposte:


96

In zsh, il percorso di ricerca della funzione ($ fpath) definisce una serie di directory, che contengono file che possono essere contrassegnati per essere caricati automaticamente quando la funzione in essi contenuta è necessaria per la prima volta.

Zsh ha due modalità di caricamento automatico dei file: la modalità nativa di Zsh e un'altra modalità che ricorda il caricamento automatico di ksh. Quest'ultimo è attivo se è impostata l'opzione KSH_AUTOLOAD. La modalità nativa di Zsh è quella predefinita e non discuterò qui in altro modo (vedi "man zshmisc" e "man zshoptions" per i dettagli sul caricamento automatico in stile ksh).

Va bene. Supponiamo che tu abbia una directory `~ / .zfunc 'e desideri che faccia parte del percorso di ricerca della funzione, fai questo:

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

Che aggiunge la directory privato al fronte del percorso di ricerca. Ciò è importante se si desidera sovrascrivere le funzioni dall'installazione di zsh con le proprie (come, quando si desidera utilizzare una funzione di completamento aggiornata come `_git 'dal repository CVS di zsh con una versione installata precedente della shell).

Vale anche la pena notare che le directory di "$ fpath" non vengono cercate in modo ricorsivo. Se desideri che la tua directory privata venga cercata in modo ricorsivo, dovrai occupartene da solo, in questo modo (il frammento seguente richiede che l'opzione `EXTENDED_GLOB 'sia impostata):

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

Può sembrare criptico a occhio nudo, ma in realtà aggiunge tutte le directory sotto `~ / .zfunc 'a` $ fpath', ignorando le directory chiamate "CVS" (che è utile, se hai intenzione di fare il checkout di un intero albero delle funzioni dal CVS di zsh nel percorso di ricerca privato).

Supponiamo che tu abbia un file `~ / .zfunc / hello 'che contiene la seguente riga:

printf 'Hello world.\n'

Tutto ciò che devi fare ora è contrassegnare la funzione da caricare automaticamente al suo primo riferimento:

autoload -Uz hello

"Di cosa parla -Uz?", Chiedi? Bene, questa è solo una serie di opzioni che fanno sì che `autoload 'faccia la cosa giusta, indipendentemente da quali opzioni vengano impostate diversamente. La `U 'disabilita l'espansione dell'alias mentre la funzione viene caricata e la' z 'forza il caricamento automatico in stile zsh anche se` KSH_AUTOLOAD' è impostato per qualsiasi motivo.

Dopo che è stato curato, puoi usare la tua nuova funzione "ciao":

ciao zsh%
Ciao mondo.

Una parola sull'approvvigionamento di questi file: è sbagliato . Se si procurasse quel file `~ / .zfunc / hello ', si stampa semplicemente" Ciao mondo ". una volta. Niente di più. Nessuna funzione sarà definita. Inoltre, l'idea è di caricare il codice della funzione solo quando è necessario . Dopo la chiamata di `autoload 'la definizione della funzione non viene letta. La funzione è appena contrassegnata per essere caricata automaticamente in seguito, se necessario.

E infine, una nota su $ FPATH e $ fpath: Zsh mantiene quelli come parametri collegati. Il parametro minuscolo è un array. La versione maiuscola è una stringa scalare, che contiene le voci dell'array collegato unite da due punti tra le voci. Questo viene fatto, poiché la gestione di un elenco di scalari è molto più naturale utilizzando le matrici, pur mantenendo la compatibilità con le versioni precedenti per il codice che utilizza il parametro scalare. Se scegli di utilizzare $ FPATH (quello scalare), devi fare attenzione:

FPATH=~/.zfunc:$FPATH

funzionerà, mentre i seguenti non:

FPATH="~/.zfunc:$FPATH"

Il motivo è che l'espansione della tilde non viene eseguita tra virgolette doppie. Questa è probabilmente la fonte dei tuoi problemi. Se echo $FPATHstampa una tilde e non un percorso espanso, non funzionerà. Per sicurezza, userei $ HOME invece di una tilde come questa:

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

Detto questo, preferirei di gran lunga utilizzare il parametro array come ho fatto all'inizio di questa spiegazione.

Inoltre, non dovresti esportare il parametro $ FPATH. È necessario solo per l'attuale processo di shell e non per nessuno dei suoi figli.

Aggiornare

Per quanto riguarda il contenuto dei file in `$ fpath ':

Con il caricamento automatico in stile zsh, il contenuto di un file è il corpo della funzione che definisce. Quindi un file chiamato "ciao" contenente una riga echo "Hello world."definisce completamente una funzione chiamata "ciao". Sei libero di inserire hello () { ... }il codice, ma sarebbe superfluo.

Tuttavia, l'affermazione secondo cui un file può contenere solo una funzione non è del tutto corretta.

Soprattutto se guardi alcune funzioni dal sistema di completamento basato sulle funzioni (compsys) ti renderai presto conto che si tratta di un malinteso. Sei libero di definire funzioni aggiuntive in un file di funzioni. Sei anche libero di eseguire qualsiasi tipo di inizializzazione, che potrebbe essere necessario eseguire la prima volta che viene chiamata la funzione. Tuttavia, quando lo fai definirai sempre una funzione che è denominata come il file nel file e la chiamerai alla fine del file, in modo che venga eseguita la prima volta che viene fatto riferimento alla funzione.

Se - con le sotto-funzioni - non hai definito una funzione chiamata come il file all'interno del file, finiresti con quella funzione che contiene definizioni di funzioni (cioè quelle delle sotto-funzioni nel file). Definiresti effettivamente tutte le tue sotto-funzioni ogni volta che chiami la funzione che è chiamata come il file. Normalmente, non è quello che vuoi, quindi potresti ridefinire una funzione, che ha il nome del file all'interno del file.

Includerò uno scheletro corto, che ti darà un'idea di come funziona:

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

Se avessi eseguito questo esempio sciocco, la prima corsa sarebbe simile a questa:

ciao zsh%
inizializzazione ...
Ciao mondo.

E le chiamate consecutive appariranno così:

ciao zsh%
Ciao mondo.

Spero che questo chiarisca le cose.

(Uno degli esempi più complessi del mondo reale che utilizza tutti questi trucchi è la già citata funzione ` _tmux 'dal sistema di completamento basato sulle funzioni di zsh.)


Grazie Frank! Ho letto nelle altre risposte che posso definire solo una funzione per file, giusto? Ho notato che non hai usato la sintassi my_function () { }nel tuo Hello worldesempio. Se la sintassi non è necessaria, quando sarebbe utile usarla?
Amelio Vazquez-Reina

1
Ho esteso la risposta originale anche a quelle domande.
Frank Terbeck,

"Definirai sempre in funzione che è chiamato come il file nel file e chiamerai quella funzione alla fine del file": perché?
Hibou57,

Hibou57: (A parte: è un errore di battitura lì, dovrebbe essere "definire una funzione", che è stato risolto ora.) Ho pensato che fosse chiaro, quando prendi in considerazione lo snippet di codice che segue. Ad ogni modo, ho aggiunto un paragrafo che spiega in modo più letterale il motivo.
Frank Terbeck,

Ecco maggiori informazioni dal tuo sito, grazie.
Timo

5

Il nome del file in una directory denominata da un fpathelemento deve corrispondere al nome della funzione di caricamento automatico che definisce.

La tua funzione è denominata my_functioned ~/.my_zsh_functionsè la directory desiderata nella tua fpath, quindi la definizione di my_functiondovrebbe essere nel file ~/.my_zsh_functions/my_function.

Il plurale nel nome file proposto ( functions_1) indica che si stava pianificando di inserire più funzioni nel file. Non è così fpathche funziona il caricamento automatico. Dovresti avere una definizione di funzione per file.


2

dare source ~/.my_zsh_functions/functions1nel terminale e valutare my_function, ora sarai in grado di chiamare la funzione


2
Grazie, ma qual è il ruolo di FPATHe autoloadpoi? Perché devo anche eseguire il sorgente del file? Vedi la mia domanda aggiornata.
Amelio Vazquez-Reina

1

Puoi "caricare" un file con tutte le tue funzioni nel tuo $ ZDOTDIR / .zshrc in questo modo:

source $ZDOTDIR/functions_file

Oppure puoi usare un punto "." invece di "fonte".


1
Grazie, ma qual è il ruolo di FPATHe autoloadpoi? Perché devo anche eseguire il sorgente del file? Vedi la mia domanda aggiornata.
Amelio Vazquez-Reina

0

Sourcing sicuramente non è l'approccio giusto, dal momento che ciò che sembra desiderare è avere funzioni inizializzate pigre. Questo è ciò che autoloadserve. Ecco come realizzi ciò che cerchi.

Nel tuo ~/.my_zsh_functions, dici di voler mettere una funzione chiamata my_functionechos "ciao mondo". Ma lo avvolgi in una chiamata di funzione, che non è come funziona. Invece, devi creare un file chiamato ~/.my_zsh_functions/my_function. In esso, semplicemente echo "Hello world", non in un wrapper di funzioni. Potresti anche fare qualcosa del genere se preferisci davvero avere il 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

Successivamente, nel tuo .zshrcfile, aggiungi quanto segue:

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

Quando si carica una nuova shell ZSH, digitare which my_function. Dovresti vedere questo:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH ha appena eliminato la mia funzione per te autoload -X. Adesso corri my_functionma stai solo scrivendo my_function. Dovresti vedere la Hello worldstampa e ora quando esegui which my_functiondovresti vedere la funzione compilata in questo modo:

my_function () {
    echo "Hello world"
}

Ora, la vera magia arriva quando imposti l'intera ~/.my_zsh_functionscartella con cui lavorare autoload. Se vuoi che tutti i file rilasciati in questa cartella funzionino in questo modo, cambia ciò che metti in .zshrcqualcosa in questo:

# 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.