Як визначити та завантажити власну функцію оболонки в zsh


54

Мені важко визначити та запустити власні функції оболонки в 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, що є правильним значенням майже для всіх.
Френк Тербек

Відповіді:


95

У zsh шлях пошуку функції ($ fpath) визначає набір каталогів, які містять файли, які можуть бути позначені для завантаження автоматично, коли функція, яку вони містять, потрібна вперше.

Zsh має два режими автозавантаження файлів: рідний спосіб Zsh та інший режим, що нагадує автоматичне завантаження ksh. Останній активний, якщо встановлено параметр KSH_AUTOLOAD. Рідний режим Zsh є типовим, і я не буду обговорювати іншого шляху (див. "Man zshmisc" та "man zshoptions" для деталей про автоматичне завантаження у стилі ksh).

Добре. Скажіть, у вас є каталог `~ / .zfunc ', і ви хочете, щоб він був частиною шляху пошуку функції, зробіть це:

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

Це додає ваш приватний каталог на передню частину шляху пошуку. Це важливо, якщо ви хочете замінити функції з установки zsh своїми власними (наприклад, коли ви хочете використовувати оновлену функцію завершення, наприклад `_git 'із сховища CVS zsh із старішою встановленою версією оболонки).

Варто також зазначити, що каталоги з $ $ fpath не шукаються рекурсивно. Якщо ви хочете, щоб ваш приватний каталог рекурсивно шукався, вам доведеться подбати про це самостійно, як це (наступний фрагмент вимагає встановити опцію `EXTENDED_GLOB '):

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

Це може виглядати недосвідченим для очей, але це дійсно просто додає всі каталоги нижче `~ / .zfunc 'до` $ fpath', ігноруючи каталоги під назвою "CVS" (що корисно, якщо ви плануєте перевірити ціле дерево функцій із CVS zsh у ваш приватний шлях пошуку).

Припустимо, ви отримали файл `~ / .zfunc / hello ', який містить такий рядок:

printf 'Hello world.\n'

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

autoload -Uz hello

"Про що йдеться -Uz?", Запитаєте ви? Ну, це лише набір параметрів, які змусять автозавантаження робити правильно, незалежно від того, які параметри встановлюються інакше. "U" вимикає розширення псевдоніму під час завантаження функції, а "z" примушує автоматично завантажувати стиль zsh, навіть якщо з будь-якої причини встановлено `KSH_AUTOLOAD '.

Після того, як це буде зроблено, ви можете скористатися новою функцією `привіт ':

zsh% привіт
Привіт Світ.

Слово про пошук цих файлів: Це просто неправильно . Якщо ви створили б файл ~ ~ / .zfunc / hello, він просто надрукував би "Здрастуй, світ". один раз. Більше нічого. Жодна функція не буде визначена. І крім того, ідея полягає у завантаженні коду функції лише тоді, коли це потрібно . Після виклику автозавантаження визначення функції не зчитується. Функція просто позначена для автоматичного завантаження пізніше.

І нарешті, примітка про $ FPATH та $ fpath: Zsh підтримує їх як пов'язані параметри. Нижній параметр регістру - це масив. Верхня версія верхнього регістру - це скалярний рядок, який містить записи з пов'язаного масиву, з'єднаного колонами між записами. Це робиться, тому що обробка списку скалярів набагато природніше за допомогою масивів, зберігаючи також сумісність для коду, що використовує скалярний параметр. Якщо ви вирішили використовувати $ FPATH (скалярний), вам потрібно бути обережним:

FPATH=~/.zfunc:$FPATH

буде працювати, тоді як наступне не буде:

FPATH="~/.zfunc:$FPATH"

Причина полягає в тому, що розширення тильди не виконується в подвійних лапках. Це, ймовірно, джерело ваших проблем. Якщо echo $FPATHдрукує тильду, а не розгорнутий шлях, це не спрацює. Для безпеки я б використав $ HOME замість тильди на зразок цього:

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

Зважаючи на це, я скоріше використовую параметр масиву, як я це робив у верхній частині цього пояснення.

Ви також не повинні експортувати параметр $ FPATH. Він потрібен лише поточному процесу оболонки, а не будь-якому з його дітей.

Оновлення

Щодо вмісту файлів у `$ fpath ':

При автоматичному завантаженні в стилі zsh вміст файлу є частиною функції, яку він визначає. Таким чином, файл з назвою "привіт", що містить рядок, echo "Hello world."повністю визначає функцію, що називається "привіт". Ви можете ввести 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, тепер ви зможете викликати функцію


2
Спасибі, але яка роль FPATHі autoloadтоді? Чому мені також потрібно джерело файлу? Дивіться моє оновлене запитання.
Амеліо Васкес-Рейна

1

Ви можете "завантажити" файл із усіма своїми функціями у вашому $ ZDOTDIR / .zshrc таким чином:

source $ZDOTDIR/functions_file

Або можна використовувати крапку "". замість "джерела".


1
Спасибі, але яка роль FPATHі autoloadтоді? Чому мені також потрібно джерело файлу? Дивіться моє оновлене запитання.
Амеліо Васкес-Рейна

0

Пошукові роботи, безумовно, не є правильним підходом, оскільки, здається, ви хочете, щоб це ледачі ініціалізовані функції. Ось для чого 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 щойно заробив для вас мою функцію 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.