Jak zdefiniować i załadować własną funkcję powłoki w zsh

54

Trudno mi zdefiniować i uruchomić własne funkcje powłoki w Zsh. Postępowałem zgodnie z instrukcjami w oficjalnej dokumentacji i najpierw spróbowałem z prostym przykładem, ale nie udało mi się go uruchomić.

Mam folder:

~/.my_zsh_functions

W tym folderze mam plik o nazwie functions_1z rwxuprawnieniami użytkownika. W tym pliku zdefiniowałem następującą funkcję powłoki:

my_function () {
echo "Hello world";
}

Zdefiniowałem, FPATHaby dołączyć ścieżkę do folderu ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Mogę potwierdzić, że folder .my_zsh_functionsznajduje się w ścieżce funkcji za pomocą echo $FPATHlubecho $fpath

Jeśli jednak spróbuję wykonać następujące czynności z powłoki:

> autoload my_function
> my_function

Dostaję:

zsh: moja_test_funkcja: nie znaleziono pliku definicji funkcji

Czy jest coś jeszcze, co muszę zrobić, aby móc zadzwonić my_function?

Aktualizacja:

Dotychczasowe odpowiedzi sugerują pozyskiwanie pliku za pomocą funkcji zsh. To ma sens, ale jestem trochę zdezorientowany. Czy Zsh nie powinien wiedzieć, gdzie są te pliki FPATH? Jaki jest zatem cel autoloadtego?

Amelio Vazquez-Reina
źródło
Upewnij się, że $ ZDOTDIR jest poprawnie zdefiniowany. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
Wartość $ ZDOTDIR nie jest związana z tym problemem. Zmienna określa, gdzie zsh szuka plików konfiguracyjnych użytkownika. Jeśli nie jest ustawiony, zamiast tego używa się $ HOME, co jest właściwą wartością dla prawie wszystkich.
Frank Terbeck

Odpowiedzi:

95

W zsh ścieżka wyszukiwania funkcji ($ fpath) definiuje zestaw katalogów, które zawierają pliki, które można oznaczyć do automatycznego załadowania, gdy funkcja, którą zawierają, jest potrzebna po raz pierwszy.

Zsh ma dwa tryby automatycznego ładowania plików: rodzimy sposób Zsha i inny tryb podobny do automatycznego ładowania ksh. Ten ostatni jest aktywny, jeśli ustawiona jest opcja KSH_AUTOLOAD. Tryb macierzysty Zsh jest domyślny i nie będę tutaj omawiał innych sposobów (zobacz „man zshmisc” i „man zshoptions”, aby uzyskać szczegółowe informacje na temat automatycznego ładowania w stylu ksh).

W porządku. Załóżmy, że masz katalog `~ / .zfunc 'i chcesz, aby był on częścią ścieżki wyszukiwania funkcji, wykonaj następujące czynności:

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

To dodaje twój prywatny katalog na początku ścieżki wyszukiwania. Jest to ważne, jeśli chcesz zastąpić funkcje z instalacji zsh własną (np. Jeśli chcesz użyć zaktualizowanej funkcji uzupełniania, takiej jak `_git 'z repozytorium CVS zsh ze starszą zainstalowaną wersją powłoki).

Warto również zauważyć, że katalogi z `$ fpath 'nie są przeszukiwane rekurencyjnie. Jeśli chcesz, aby twój prywatny katalog był przeszukiwany rekurencyjnie, musisz sam o to zadbać, w ten sposób (poniższy fragment wymaga ustawienia opcji `EXTENDED_GLOB '):

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

Może to wyglądać tajemniczo dla niewprawnego oka, ale tak naprawdę dodaje wszystkie katalogi poniżej `~ / .zfunc 'do` $ fpath', ignorując katalogi o nazwie „CVS” (co jest przydatne, jeśli planujesz wykupić całość drzewo funkcji z CVS zsh do twojej prywatnej ścieżki wyszukiwania).

Załóżmy, że masz plik `~ / .zfunc / hello ', który zawiera następujący wiersz:

printf 'Hello world.\n'

Wszystko, co musisz teraz zrobić, to zaznaczyć funkcję, która zostanie automatycznie załadowana po pierwszym odwołaniu:

autoload -Uz hello

„O co chodzi -Uz?”, Pytasz? Cóż, to tylko zestaw opcji, które spowodują, że `autoload 'zrobi właściwą rzecz, bez względu na to, jakie opcje są ustawione inaczej. `U 'wyłącza rozszerzenie aliasu podczas ładowania funkcji, a` z' wymusza automatyczne ładowanie w stylu zsh, nawet jeśli `KSH_AUTOLOAD 'jest ustawiony z jakiegokolwiek powodu.

Po zakończeniu tej czynności możesz użyć nowej funkcji hello:

zsh% witam
Witaj świecie.

Słowo o pozyskiwaniu tych plików: to po prostu źle . Jeśli źródłowy plik „~ / .zfunc / hello”, po prostu wydrukuje „Hello world”. pewnego razu. Nic więcej. Żadna funkcja nie zostanie zdefiniowana. Poza tym pomysł polega na ładowaniu kodu funkcji tylko wtedy, gdy jest to wymagane . Po wywołaniu `autoload 'definicja funkcji nie jest czytana. Funkcja jest właśnie oznaczona do automatycznego ładowania później w razie potrzeby.

I wreszcie uwaga na temat $ FPATH i $ fpath: Zsh zachowuje je jako parametry powiązane. Parametr pisany małymi literami to tablica. Wersja z dużymi literami jest skalarnym ciągiem znaków, który zawiera wpisy z połączonej tablicy połączone dwukropkami pomiędzy wpisami. Odbywa się to, ponieważ obsługa listy skalarów jest znacznie bardziej naturalna przy użyciu tablic, przy jednoczesnym zachowaniu kompatybilności wstecznej dla kodu korzystającego z parametru skalarnego. Jeśli wybierzesz opcję $ FPATH (skalarną), musisz zachować ostrożność:

FPATH=~/.zfunc:$FPATH

będzie działać, podczas gdy następujące nie będą:

FPATH="~/.zfunc:$FPATH"

Powodem jest to, że interpretacja tyldy nie jest wykonywana w podwójnych cudzysłowach. Jest to prawdopodobnie źródło twoich problemów. Jeśli echo $FPATHdrukuje tyldę, a nie rozwiniętą ścieżkę, to nie będzie działać. Dla bezpieczeństwa użyłbym $ HOME zamiast tyldy takiej jak ta:

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

To powiedziawszy, wolałbym raczej użyć parametru tablicy, jak na początku tego objaśnienia.

Nie powinieneś również eksportować parametru $ FPATH. Jest potrzebny tylko w bieżącym procesie powłoki, a nie przez żadne z jego potomków.

Aktualizacja

Odnośnie zawartości plików w `$ fpath ':

W przypadku automatycznego ładowania w stylu zsh zawartość pliku stanowi treść zdefiniowanej przez niego funkcji. Zatem plik o nazwie „cześć” zawierający wiersz echo "Hello world."całkowicie definiuje funkcję o nazwie „cześć”. Możesz hello () { ... }ominąć kod, ale byłoby to zbyteczne.

Twierdzenie, że jeden plik może zawierać tylko jedną funkcję, nie jest jednak całkowicie poprawne.

Zwłaszcza jeśli spojrzysz na niektóre funkcje z systemu uzupełniania opartego na funkcjach (compsys), szybko zdasz sobie sprawę, że jest to nieporozumienie. Możesz dowolnie definiować dodatkowe funkcje w pliku funkcji. Możesz także wykonać dowolną inicjalizację, która może być konieczna przy pierwszym wywołaniu funkcji. Jednak gdy to zrobisz, zawsze zdefiniujesz funkcję o nazwie takiej jak plik w pliku i wywołasz tę funkcję na końcu pliku, aby była uruchamiana przy pierwszym wywołaniu funkcji.

Jeśli - z podfunkcjami - nie zdefiniowałeś funkcji o nazwie takiej jak plik w pliku, skończyłbyś z tą funkcją zawierającą definicje funkcji (mianowicie definicje podfunkcji w pliku). Skutecznie definiowałbyś wszystkie swoje podfunkcje przy każdym wywołaniu funkcji o nazwie takiej jak plik. Zwykle nie jest to pożądane, więc ponownie zdefiniujesz funkcję, która nazywa się jak plik w pliku.

Dołączę krótki szkielet, który da ci wyobrażenie o tym, jak to działa:

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

Gdybyś uruchomił ten głupi przykład, pierwszy przebieg wyglądałby tak:

zsh% witam
inicjowanie ...
Witaj świecie.

Kolejne połączenia będą wyglądać następująco:

zsh% witam
Witaj świecie.

Mam nadzieję, że to wszystko wyjaśni.

(Jednym z bardziej złożonych przykładów, w których wykorzystuje się wszystkie te sztuczki, jest wspomniana już funkcja ` _tmux 'z systemu uzupełniania opartego na funkcji zsh.)

Frank Terbeck
źródło
Dzięki, Frank! Przeczytałem w innych odpowiedziach, że mogę zdefiniować tylko jedną funkcję na plik, prawda? Zauważyłem, że nie użyłeś składni my_function () { }w swoim Hello worldprzykładzie. Jeśli składnia nie jest potrzebna, kiedy warto jej użyć?
Amelio Vazquez-Reina,
1
Rozszerzyłem oryginalną odpowiedź, aby również odpowiedzieć na te pytania.
Frank Terbeck,
„Zawsze będziesz definiować funkcję o nazwie takiej jak plik w pliku i wywoływać tę funkcję na końcu pliku”: dlaczego tak jest?
Hibou57,
Hibou57: (Poza tym: tam jest literówka, powinno być „zdefiniować funkcję”, to już naprawiono.) Pomyślałem, że to jasne, gdy weźmie się pod uwagę fragment kodu. W każdym razie dodałem akapit, który wyjaśnia przyczynę nieco dosłownie.
Frank Terbeck,
Oto więcej informacji z Twojej witryny, dzięki.
Timo,
5

Nazwa pliku w katalogu nazwanym przez fpathelement musi być zgodna z nazwą zdefiniowanej funkcji autoloadable.

Twoja funkcja ma nazwę my_functioni ~/.my_zsh_functionsjest zamierzonym katalogiem w twoim fpath, więc definicja my_functionpowinna znajdować się w pliku ~/.my_zsh_functions/my_function.

Liczba mnoga w proponowanej nazwie pliku ( functions_1) wskazuje, że planujesz umieścić wiele funkcji w pliku. To nie fpathdziała i automatyczne ładowanie działa. Powinieneś mieć jedną definicję funkcji na plik.

Chris Johnsen
źródło
2

podaj source ~/.my_zsh_functions/functions1terminal i oceń my_function, teraz będziesz mógł wywołać funkcję

harish.venkat
źródło
2
Dzięki, ale jaka jest rola FPATHi autoloadwtedy? Dlaczego muszę również źródło pliku? Zobacz moje zaktualizowane pytanie.
Amelio Vazquez-Reina,
1

Możesz „załadować” plik ze wszystkimi swoimi funkcjami do $ ZDOTDIR / .zshrc w następujący sposób:

source $ZDOTDIR/functions_file

Lub można użyć kropki „.” zamiast „źródła”.

ramonowski
źródło
1
Dzięki, ale jaka jest rola FPATHi autoloadwtedy? Dlaczego muszę również źródło pliku? Zobacz moje zaktualizowane pytanie.
Amelio Vazquez-Reina,
0

Sourcing zdecydowanie nie jest właściwym podejściem, ponieważ wydaje się, że chcesz mieć leniwe funkcje inicjowane. Po to autoloadjest. Oto, w jaki sposób osiągasz to, czego szukasz.

W twojej ~/.my_zsh_functions, mówisz, że chcesz umieścić funkcję o nazwie my_functionże echo „Hello World”. Ale zawijasz je w wywołanie funkcji, co nie działa w ten sposób. Zamiast tego musisz utworzyć plik o nazwie ~/.my_zsh_functions/my_function. W nim po prostu umieść echo "Hello world", a nie w opakowaniu funkcji. Możesz również zrobić coś takiego, jeśli naprawdę wolisz mieć opakowanie.

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

Następnie w swoim .zshrcpliku dodaj:

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

Po załadowaniu nowej powłoki ZSH wpisz which my_function. Powinieneś to zobaczyć:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH właśnie usunął dla ciebie moją funkcję autoload -X. Teraz biegnij, my_functionale po prostu pisz my_function. Powinieneś zobaczyć Hello worldwydruk, a teraz po uruchomieniu which my_functionpowinieneś zobaczyć wypełnioną funkcję w następujący sposób:

my_function () {
    echo "Hello world"
}

Teraz prawdziwa magia pojawia się po skonfigurowaniu całego ~/.my_zsh_functionsfolderu do pracy autoload. Jeśli chcesz, aby każdy plik, który upuściłeś w tym folderze, działał w ten sposób, zmień to, co umieściłeś w swoim folderze .zshrcna coś takiego:

# 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
źródło