Как определить и загрузить свою собственную функцию оболочки в Zsh

55

Мне трудно определить и запустить свои собственные функции оболочки в Zsh. Я следовал инструкциям на официальной документации и сначала попробовал простой пример, но мне не удалось это сделать.

У меня есть папка:

~/.my_zsh_functions

В этой папке у меня есть файл functions_1с rwxразрешениями пользователя. В этом файле я определил следующую функцию оболочки:

my_function () {
echo "Hello world";
}

Я определил, FPATHчтобы включить путь к папке ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Я могу подтвердить, что папка .my_zsh_functionsнаходится в пути функций с помощью echo $FPATHилиecho $fpath

Однако, если я тогда попробую следующее из оболочки:

> autoload my_function
> my_function

Я получил:

zsh: my_test_function: файл определения функции не найден

Есть ли что-то еще, что мне нужно сделать, чтобы иметь возможность позвонить my_function?

Обновить:

Ответы до сих пор предполагают поиск файла с помощью функций zsh. Это имеет смысл, но я немного растерялся. Разве zsh не должен знать, где находятся эти файлы FPATH? Какова цель autoloadтогда?

Амелио Васкес-Рейна
источник
Убедитесь, что у вас правильно определен $ ZDOTDIR. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
Значение $ ZDOTDIR не связано с этой проблемой. Переменная определяет, где zsh ищет файлы конфигурации пользователя. Если он не установлен, вместо него используется $ HOME, что является правильным значением почти для всех.
Фрэнк Тербек

Ответы:

96

В zsh путь поиска функции ($ fpath) определяет набор каталогов, которые содержат файлы, которые могут быть помечены для автоматической загрузки, когда функция, которую они содержат, требуется в первый раз.

Zsh имеет два режима автозагрузки файлов: собственный способ Zsh и другой режим, который напоминает автозагрузку ksh. Последний активен, если установлена ​​опция KSH_AUTOLOAD. Нативный режим Zsh используется по умолчанию, и я не буду обсуждать здесь другие способы (см. «Man zshmisc» и «man zshoptions» для получения подробной информации о автозагрузке в стиле ksh).

Хорошо. Скажем, у вас есть каталог `~ / .zfunc ', и вы хотите, чтобы он был частью пути поиска функции, вы делаете это:

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

Это добавляет ваш личный каталог в передней части пути поиска. Это важно, если вы хотите переопределить функции из установки zsh своей собственной (например, когда вы хотите использовать обновленную функцию завершения, такую ​​как _git из репозитория zsh CVS, с более старой установленной версией оболочки).

Стоит также отметить, что каталоги из $ fpath не ищутся рекурсивно. Если вы хотите, чтобы ваш личный каталог подвергался рекурсивному поиску, вам придется позаботиться об этом самостоятельно, как показано ниже (в следующем фрагменте кода должна быть задана опция EXTENDED_GLOB):

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

Это может показаться загадочным для неопытного глаза, но на самом деле он просто добавляет все каталоги ниже `~ / .zfunc 'в` $ fpath', игнорируя при этом каталоги с именем "CVS" (что полезно, если вы планируете оформить заказ целиком. дерево функций из CVS zsh в ваш личный путь поиска).

Предположим, вы получили файл `~ / .zfunc / hello ', который содержит следующую строку:

printf 'Hello world.\n'

Все, что вам нужно сделать сейчас, это отметить функцию, которая будет автоматически загружена при первом обращении к ней:

autoload -Uz hello

«Что такое -Уз?», Спросите вы? Ну, это всего лишь набор опций, которые заставят "автозагрузку" работать правильно, независимо от того, какие опции устанавливаются в противном случае. `U 'отключает расширение псевдонимов во время загрузки функции, а` z' вызывает автозагрузку в стиле zsh, даже если `KSH_AUTOLOAD 'установлен по любой причине.

После этого вы можете использовать новую функцию `hello ':

zsh% привет
Привет, мир.

Несколько слов о поиске этих файлов: это просто неправильно . Если вы поставите этот файл `~ / .zfunc / hello ', он просто напечатает« Hello world ». один раз. Ничего больше. Никакая функция не будет определена. Кроме того, идея заключается в том, чтобы загружать код функции только тогда, когда это требуется . После вызова автозагрузки определение функции не читается. Функция просто помечена для автозагрузки позже при необходимости.

И, наконец, заметка о $ FPATH и $ fpath: Zsh поддерживает их как связанные параметры. Параметр в нижнем регистре является массивом. Версия в верхнем регистре представляет собой строковый скаляр, который содержит записи из связанного массива, объединенные двоеточиями между записями. Это сделано, потому что обработка списка скаляров более естественна при использовании массивов, но при этом поддерживается обратная совместимость для кода, использующего параметр скаляра. Если вы решите использовать $ FPATH (скалярный), вам нужно быть осторожным:

FPATH=~/.zfunc:$FPATH

будет работать, а следующее не будет:

FPATH="~/.zfunc:$FPATH"

Причина в том, что расширение тильды не выполняется в двойных кавычках. Это, вероятно, источник ваших проблем. Если echo $FPATHпечатать тильду, а не расширенный путь, это не будет работать. Чтобы быть в безопасности, я бы использовал $ HOME вместо тильды, подобной этой:

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

Тем не менее, я бы предпочел использовать параметр массива, как я сделал в верхней части этого объяснения.

Вы также не должны экспортировать параметр $ FPATH. Он нужен только текущему процессу оболочки, а не его дочерним элементам.

Обновить

Относительно содержимого файлов в `$ fpath ':

При автозагрузке в стиле zsh содержимое файла является телом функции, которую он определяет. Таким образом, файл с именем «hello», содержащий строку, echo "Hello world."полностью определяет функцию с именем «hello». Вы можете свободно размещать hello () { ... }код, но это было бы излишним.

Однако утверждение, что один файл может содержать только одну функцию, не совсем корректно.

Особенно если вы посмотрите на некоторые функции из системы завершения, основанной на функциях (compsys), вы быстро поймете, что это заблуждение. Вы можете определить дополнительные функции в файле функций. Вы также можете выполнять любую инициализацию, которая может потребоваться при первом вызове функции. Однако, когда вы это сделаете, вы всегда определите функцию, которая названа как файл в файле, и вызовите эту функцию в конце файла, так что она будет запущена при первом обращении к функции.

Если - с подфункциями - вы не определили функцию, названную как файл в файле, вы бы в конечном итоге получили эту функцию с определениями функций в ней (а именно с теми из подфункций в файле). Вы бы эффективно определяли все свои подфункции каждый раз, когда вызываете функцию с именем, подобным файлу. Обычно это не то, что вам нужно, поэтому вы должны переопределить функцию, которая называется как файл в файле.

Я включу короткий скелет, который даст вам представление о том, как это работает:

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

Если вы запустите этот глупый пример, первый запуск будет выглядеть так:

zsh% привет
инициализация ...
Привет, мир.

И последовательные звонки будут выглядеть так:

zsh% привет
Привет, мир.

Я надеюсь, что это проясняет ситуацию.

(Один из более сложных реальных примеров, использующих все эти приемы, - это уже упоминавшаяся функция _tmux из системы завершения, основанной на функциях zsh.)

Фрэнк Тербек
источник
Спасибо Фрэнк! В других ответах я прочитал, что я могу определить только одну функцию на файл, верно? Я заметил, что вы не использовали синтаксис my_function () { }в вашем Hello worldпримере. Если синтаксис не нужен, когда будет полезно его использовать?
Амелио Васкес-Рейна
1
Я расширил первоначальный ответ, чтобы ответить и на эти вопросы.
Фрэнк Тербек
«Вы всегда будете определять в функции, которая названа как файл в файле, и вызывать эту функцию в конце файла»: почему так?
Hibou57
Hibou57: (В стороне: это опечатка, должно быть «определить функцию», это исправлено). Я подумал, что это было понятно, если принять во внимание следующий фрагмент кода. Во всяком случае, я добавил параграф, который объясняет причину чуть более буквально.
Франк Тербек
Вот больше информации с вашего сайта, спасибо.
Тимо
5

Имя файла в каталоге, названном fpathэлементом, должно совпадать с именем функции автозагрузки, которую он определяет.

Ваша функция названа my_functionи ~/.my_zsh_functionsявляется предполагаемой директорией в вашей fpath, поэтому определение my_functionдолжно быть в файле ~/.my_zsh_functions/my_function.

Множественное число в предложенном вами имени файла ( functions_1) указывает, что вы планируете поместить в файл несколько функций. Это не так, как fpathи автозагрузка работы. Вы должны иметь одно определение функции для каждого файла.

Крис Джонсен
источник
2

дайте source ~/.my_zsh_functions/functions1в терминал и оцените my_function, теперь вы сможете вызывать функцию

harish.venkat
источник
2
Спасибо, но какова роль FPATHи autoloadтогда? Зачем мне также нужен исходный файл? Смотрите мой обновленный вопрос.
Амелио Васкес-Рейна
1

Вы можете «загрузить» файл со всеми вашими функциями в ваш $ ZDOTDIR / .zshrc следующим образом:

source $ZDOTDIR/functions_file

Или можете использовать точку "." вместо «источника».

ramonovski
источник
1
Спасибо, но какова роль FPATHи autoloadтогда? Зачем мне также нужен исходный файл? Смотрите мой обновленный вопрос.
Амелио Васкес-Рейна
0

Sourcing определенно не является правильным подходом, так как вам кажется, что нужно иметь ленивые инициализированные функции. Вот для чего autoload. Вот как вы делаете то, что вы после.

В вашем ~/.my_zsh_functions, вы говорите, что хотите поместить функцию под названием my_function«Привет, мир». Но вы заключаете его в вызов функции, а это не так. Вместо этого вам нужно создать файл с именем ~/.my_zsh_functions/my_function. В нем, просто положить echo "Hello world", а не в обертку функции. Вы также можете сделать что-то вроде этого, если вы действительно предпочитаете иметь обертку.

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

Далее в вашем .zshrcфайле добавьте следующее:

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

Когда вы загрузите новую оболочку ZSH, введите which my_function. Вы должны увидеть это:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH только что отключил my_function для вас autoload -X. Теперь беги, my_functionно просто печатай my_function. Вы должны увидеть Hello worldраспечатку, и теперь, когда вы запустите, which my_functionвы увидите заполненную функцию следующим образом:

my_function () {
    echo "Hello world"
}

Теперь настоящая магия приходит, когда вы настраиваете всю свою ~/.my_zsh_functionsпапку для работы autoload. Если вы хотите, чтобы каждый файл, который вы перетащили в эту папку, работал так, измените то, что вы положили в него, .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
источник