Cómo definir y cargar su propia función de shell en zsh

55

Me está costando definir y ejecutar mis propias funciones de shell en zsh. Seguí las instrucciones en la documentación oficial e intenté con un ejemplo sencillo primero, pero no pude hacerlo funcionar.

Tengo una carpeta:

~/.my_zsh_functions

En esta carpeta tengo un archivo llamado functions_1con rwxpermisos de usuario. En este archivo tengo definida la siguiente función de shell:

my_function () {
echo "Hello world";
}

Definí FPATHincluir la ruta a la carpeta ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Puedo confirmar que la carpeta .my_zsh_functionsestá en la ruta de funciones con echo $FPATHoecho $fpath

Sin embargo, si luego intento lo siguiente desde el shell:

> autoload my_function
> my_function

Yo obtengo:

zsh: my_test_function: archivo de definición de función no encontrado

¿Hay algo más que deba hacer para poder llamar my_function?

Actualizar:

Las respuestas hasta ahora sugieren obtener el archivo con las funciones zsh. Esto tiene sentido, pero estoy un poco confundido. ¿No debería saber zsh dónde están esos archivos FPATH? ¿Cuál es el propósito de autoloadentonces?

Amelio Vazquez-Reina
fuente
Asegúrese de tener el $ ZDOTDIR correctamente definido. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
El valor de $ ZDOTDIR no está relacionado con este problema. La variable define dónde está buscando zsh los archivos de configuración de un usuario. Si no está configurado, se usa $ HOME, que es el valor correcto para casi todos.
Frank Terbeck

Respuestas:

96

En zsh, la ruta de búsqueda de funciones ($ fpath) define un conjunto de directorios que contienen archivos que se pueden marcar para cargarse automáticamente cuando se necesita por primera vez la función que contienen.

Zsh tiene dos modos de carga automática de archivos: la forma nativa de Zsh y otro modo que se asemeja a la carga automática de ksh. Este último está activo si se establece la opción KSH_AUTOLOAD. El modo nativo de Zsh es el predeterminado y no hablaré de la otra manera aquí (consulte "man zshmisc" y "man zshoptions" para obtener detalles sobre la carga automática de estilo ksh).

Bueno. Supongamos que tiene un directorio `~ / .zfunc 'y desea que forme parte de la ruta de búsqueda de funciones, haga esto:

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

Eso agrega su directorio privado al frente de la ruta de búsqueda. Eso es importante si desea anular las funciones de la instalación de zsh con las suyas propias (por ejemplo, cuando desea utilizar una función de finalización actualizada como `_git 'del repositorio CVS de zsh con una versión instalada más antigua del shell).

También vale la pena señalar que los directorios de `$ fpath 'no se buscan de forma recursiva. Si desea que su directorio privado se busque de forma recursiva, tendrá que ocuparse de eso usted mismo, de esta manera (el siguiente fragmento requiere que se configure la opción `EXTENDED_GLOB '):

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

Puede parecer críptico para el ojo inexperto, pero realmente solo agrega todos los directorios debajo de '~ / .zfunc' a '$ fpath', mientras ignora los directorios llamados "CVS" (lo cual es útil, si está planeando pagar un todo árbol de funciones del CVS de zsh en su ruta de búsqueda privada).

Supongamos que tiene un archivo `~ / .zfunc / hello 'que contiene la siguiente línea:

printf 'Hello world.\n'

Todo lo que necesita hacer ahora es marcar la función que se cargará automáticamente en su primera referencia:

autoload -Uz hello

"¿De qué se trata el -Uz?" Bueno, eso es solo un conjunto de opciones que harán que la 'carga automática' haga lo correcto, sin importar qué opciones se establezcan de lo contrario. La 'U' deshabilita la expansión de alias mientras se carga la función y la 'z' fuerza la carga automática de estilo zsh incluso si 'KSH_AUTOLOAD' está configurado por cualquier razón.

Después de que se haya resuelto, puede usar su nueva función 'hola':

zsh% hola
Hola Mundo.

Una palabra sobre el abastecimiento de estos archivos: eso está mal . Si obtiene ese archivo `~ / .zfunc / hello ', simplemente imprimirá" Hola mundo ". una vez. Nada mas. No se definirá ninguna función. Y además, la idea es cargar solo el código de la función cuando sea necesario . Después de la llamada de 'carga automática', la definición de la función no se lee. La función se marca para cargarse automáticamente más tarde según sea necesario.

Y finalmente, una nota sobre $ FPATH y $ fpath: Zsh los mantiene como parámetros vinculados. El parámetro en minúscula es una matriz. La versión en mayúsculas es un escalar de cadena, que contiene las entradas de la matriz vinculada unida por dos puntos entre las entradas. Esto se hace, porque manejar una lista de escalares es mucho más natural usando matrices, al tiempo que se mantiene la compatibilidad con versiones anteriores para el código que usa el parámetro escalar. Si elige usar $ FPATH (el escalar), debe tener cuidado:

FPATH=~/.zfunc:$FPATH

funcionará, mientras que lo siguiente no funcionará:

FPATH="~/.zfunc:$FPATH"

La razón es que la expansión de tilde no se realiza entre comillas dobles. Esta es probablemente la fuente de tus problemas. Si echo $FPATHimprime una tilde y no una ruta expandida, entonces no funcionará. Para estar seguro, usaría $ HOME en lugar de una tilde como esta:

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

Dicho esto, preferiría usar el parámetro de matriz como lo hice al principio de esta explicación.

Tampoco debe exportar el parámetro $ FPATH. Solo es necesario para el proceso de shell actual y no para ninguno de sus hijos.

Actualizar

Con respecto al contenido de los archivos en `$ fpath ':

Con la carga automática de estilo zsh, el contenido de un archivo es el cuerpo de la función que define. Así, un archivo llamado "hola" que contiene una línea echo "Hello world."define completamente una función llamada "hola". Eres libre de poner hello () { ... }el código, pero eso sería superfluo.

Sin embargo, la afirmación de que un archivo solo puede contener una función no es del todo correcta.

Especialmente si observa algunas funciones del sistema de finalización basado en funciones (compsys), se dará cuenta rápidamente de que es un error. Usted es libre de definir funciones adicionales en un archivo de funciones. También es libre de hacer cualquier tipo de inicialización, que puede necesitar hacer la primera vez que se llama a la función. Sin embargo, cuando lo haga, siempre definirá una función que se denomina como el archivo en el archivo y llamará a esa función al final del archivo, por lo que se ejecuta la primera vez que se hace referencia a la función.

Si, con las subfunciones, no definió una función denominada como el archivo dentro del archivo, terminaría con esa función con definiciones de función (es decir, las de las subfunciones en el archivo). Definiría efectivamente todas sus subfunciones cada vez que llame a la función que se nombra como el archivo. Normalmente, eso no es lo que desea, por lo que redefiniría una función, que se llama como el archivo dentro del archivo.

Incluiré un esqueleto corto, que te dará una idea de cómo funciona:

# 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 ejecutara este ejemplo tonto, la primera ejecución se vería así:

zsh% hola
inicializando ...
Hola Mundo.

Y las llamadas consecutivas se verán así:

zsh% hola
Hola Mundo.

Espero que esto aclare las cosas.

(Uno de los ejemplos más complejos del mundo real que usa todos esos trucos es la función ` _tmux ' ya mencionada del sistema de finalización basado en funciones de zsh).

Frank Terbeck
fuente
Gracias Frank! Leí en las otras respuestas que solo puedo definir una función por archivo, ¿es así? Noté que no usaste la sintaxis my_function () { }en tu Hello worldejemplo. Si la sintaxis no es necesaria, ¿cuándo sería útil usarla?
Amelio Vazquez-Reina
1
Extendí la respuesta original para abordar esas preguntas también.
Frank Terbeck
“Siempre definirá en una función que se denomina como el archivo en el archivo y llamará a esa función al final del archivo”: ¿por qué?
Hibou57
Hibou57: (Aparte: ese es un error tipográfico, debería ser "definir una función", eso está arreglado ahora). Pensé que estaba claro, cuando tomas en cuenta el fragmento de código que sigue. De todos modos, he agregado un párrafo que explica la razón un poco más literalmente.
Frank Terbeck
Aquí hay más información de su sitio, gracias.
Timo
5

El nombre del archivo en un directorio nombrado por un fpathelemento debe coincidir con el nombre de la función autocargable que define.

Su función se llama my_functiony ~/.my_zsh_functionses el directorio previsto en su fpath, por lo que la definición de my_functiondebe estar en el archivo ~/.my_zsh_functions/my_function.

El plural en su nombre de archivo propuesto ( functions_1) indica que estaba planeando poner múltiples funciones en el archivo. Este no es el fpathtrabajo de carga automática. Debe tener una definición de función por archivo.

Chris Johnsen
fuente
2

entregue source ~/.my_zsh_functions/functions1el terminal y evalúe my_function, ahora podrá llamar a la función

harish.venkat
fuente
2
Gracias, pero ¿cuál es el papel FPATHy autoloadluego? ¿Por qué también necesito obtener el archivo? Ver mi pregunta actualizada.
Amelio Vazquez-Reina
1

Puede "cargar" un archivo con todas sus funciones en su $ ZDOTDIR / .zshrc de esta manera:

source $ZDOTDIR/functions_file

O puede usar un punto "." en lugar de "fuente".

ramonovski
fuente
1
Gracias, pero ¿cuál es el papel FPATHy autoloadluego? ¿Por qué también necesito obtener el archivo? Ver mi pregunta actualizada.
Amelio Vazquez-Reina
0

El aprovisionamiento definitivamente no es el enfoque correcto, ya que lo que parece querer es tener funciones de inicialización diferidas. Para eso autoloades eso . Así es como logras lo que buscas.

En tu ~/.my_zsh_functions, dices que quieres poner una función llamada my_functionese eco "hola mundo". Pero, lo envuelve en una llamada de función, que no es así como funciona. En cambio, necesita hacer un archivo llamado ~/.my_zsh_functions/my_function. En él, simplemente echo "Hello world", no en un contenedor de funciones. También podría hacer algo como esto si realmente prefiere tener el envoltorio.

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

A continuación, en su .zshrcarchivo, agregue lo siguiente:

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

Cuando cargue un nuevo shell ZSH, escriba which my_function. Deberías ver esto:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH acaba de apagar mi_función para ti autoload -X. Ahora, corre my_functionpero solo escribe my_function. Debería ver la Hello worldimpresión, y ahora, cuando ejecute which my_function, debería ver la función completada así:

my_function () {
    echo "Hello world"
}

Ahora, la verdadera magia viene cuando configuras toda tu ~/.my_zsh_functionscarpeta para trabajar autoload. Si desea que todos los archivos que suelte en esta carpeta funcionen de esta manera, cambie lo que coloca en .zshrcalgo así:

# 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
fuente