Cách xác định và tải hàm shell của riêng bạn trong zsh


54

Tôi đang gặp khó khăn khi xác định và chạy các hàm shell của riêng mình trong zsh. Tôi đã làm theo các hướng dẫn trên tài liệu chính thức và thử với ví dụ dễ dàng trước tiên, nhưng tôi đã thất bại trong việc làm cho nó hoạt động.

Tôi có một thư mục:

~/.my_zsh_functions

Trong thư mục này tôi có một tệp được gọi functions_1với rwxquyền của người dùng. Trong tệp này tôi có hàm shell sau được định nghĩa:

my_function () {
echo "Hello world";
}

Tôi đã xác định FPATHđể bao gồm đường dẫn đến thư mục ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Tôi có thể xác nhận rằng thư mục .my_zsh_functionsnằm trong đường dẫn hàm với echo $FPATHhoặcecho $fpath

Tuy nhiên, nếu tôi sau đó thử các cách sau từ shell:

> autoload my_function
> my_function

Tôi có:

zsh: my_test_function: không tìm thấy tệp định nghĩa hàm

Có điều gì khác tôi cần phải làm để có thể gọi my_function?

Cập nhật:

Các câu trả lời cho đến nay đề nghị tìm nguồn cung cấp tệp với các hàm zsh. Điều này có ý nghĩa, nhưng tôi hơi bối rối. Không nên zsh biết những tập tin đó ở FPATHđâu? Mục đích của autoloadsau đó là gì?


Đảm bảo rằng bạn có $ ZDOTDIR được xác định đúng. zsh.sourceforge.net/Intro/intro_3.html
ramonovski

2
Giá trị của $ ZDOTDIR không liên quan đến vấn đề này. Biến xác định nơi zsh đang tìm kiếm tệp cấu hình của người dùng. Nếu nó không được đặt $ HOME được sử dụng thay thế, đó là giá trị phù hợp cho hầu hết mọi người.
Frank Terbeck

Câu trả lời:


95

Trong zsh, đường dẫn tìm kiếm hàm ($ fpath) xác định một tập hợp các thư mục chứa các tệp có thể được đánh dấu để được tải tự động khi lần đầu tiên chúng chứa hàm cần thiết.

Zsh có hai chế độ tải tập tin tự động: cách riêng của Zsh và chế độ khác tương tự như tự động tải của ksh. Cái sau được kích hoạt nếu tùy chọn KSH_AUTOLOAD được đặt. Chế độ riêng của Zsh là mặc định và tôi sẽ không thảo luận theo cách khác ở đây (xem "man zshmisc" và "man zshoptions" để biết chi tiết về tự động tải kiểu ksh).

Được chứ. Giả sử bạn có một thư mục `~ / .zfunc 'và bạn muốn nó là một phần của đường dẫn tìm kiếm hàm, bạn làm điều này:

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

Điều đó thêm thư mục riêng của bạn vào phía trước của đường dẫn tìm kiếm. Điều đó rất quan trọng nếu bạn muốn ghi đè các chức năng từ cài đặt của zsh bằng chính bạn (như, khi bạn muốn sử dụng chức năng hoàn thành được cập nhật, chẳng hạn như `_git 'từ kho lưu trữ CVS của zsh với phiên bản vỏ được cài đặt cũ hơn).

Điều đáng chú ý là các thư mục từ '$ fpath' không được tìm kiếm đệ quy. Nếu bạn muốn thư mục riêng của mình được tìm kiếm đệ quy, bạn sẽ phải tự chăm sóc nó, như thế này (đoạn mã sau yêu cầu tùy chọn `EXTENDED_GLOB '):

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

Nó có thể trông khó hiểu đối với mắt chưa được huấn luyện, nhưng nó thực sự chỉ thêm tất cả các thư mục bên dưới `~ / .zfunc 'vào` $ fpath', trong khi bỏ qua các thư mục có tên" CVS "(rất hữu ích, nếu bạn dự định kiểm tra toàn bộ cây chức năng từ CVS của zsh vào đường dẫn tìm kiếm riêng tư của bạn).

Giả sử bạn có một tệp `~ / .zfunc / hello 'có chứa dòng sau:

printf 'Hello world.\n'

Tất cả những gì bạn cần làm bây giờ là đánh dấu chức năng sẽ được tự động tải theo tham chiếu đầu tiên của nó:

autoload -Uz hello

"-Uz là về cái gì?", Bạn hỏi? Chà, đó chỉ là một tập hợp các tùy chọn sẽ khiến 'tự động tải' thực hiện đúng, bất kể tùy chọn nào đang được đặt khác. `U 'vô hiệu hóa việc mở rộng bí danh trong khi chức năng đang được tải và` z' buộc tự động tải kiểu zsh ngay cả khi` KSH_AUTOLOAD 'được đặt vì bất kỳ lý do gì.

Sau đó, bạn có thể sử dụng chức năng 'xin chào' mới của mình:

xin chào
Chào thế giới.

Một từ về tìm nguồn cung ứng các tệp này: Điều đó là sai . Nếu bạn nguồn tệp `~ / .zfunc / hello ', nó sẽ chỉ in" Xin chào thế giới. " Một lần. Chỉ có bấy nhiêu thôi. Không có chức năng sẽ được xác định. Và bên cạnh đó, ý tưởng là chỉ tải mã của hàm khi được yêu cầu . Sau khi gọi 'autoload', định nghĩa của hàm không được đọc. Các chức năng chỉ được đánh dấu để tự động tải sau này khi cần thiết.

Và cuối cùng, một lưu ý về $ FPATH và $ fpath: Zsh duy trì chúng là các tham số được liên kết. Tham số chữ thường là một mảng. Phiên bản chữ hoa là một vô hướng chuỗi, chứa các mục từ mảng được liên kết được nối bởi dấu hai chấm ở giữa các mục. Điều này được thực hiện, bởi vì việc xử lý một danh sách các vô hướng là cách tự nhiên hơn khi sử dụng các mảng, trong khi vẫn duy trì khả năng tương thích ngược cho mã sử dụng tham số vô hướng. Nếu bạn chọn sử dụng $ FPATH (số vô hướng), bạn cần cẩn thận:

FPATH=~/.zfunc:$FPATH

sẽ hoạt động, trong khi sau đây sẽ không:

FPATH="~/.zfunc:$FPATH"

Lý do là việc mở rộng dấu ngã không được thực hiện trong dấu ngoặc kép. Đây có thể là nguồn gốc của vấn đề của bạn. Nếu echo $FPATHin một dấu ngã và không phải là một đường dẫn mở rộng thì nó sẽ không hoạt động. Để an toàn, tôi sẽ sử dụng $ HOME thay vì dấu ngã như thế này:

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

Điều đó đang được nói, tôi muốn sử dụng tham số mảng giống như tôi đã làm ở đầu giải thích này.

Bạn cũng không nên xuất tham số $ FPATH. Nó chỉ cần thiết cho quá trình vỏ hiện tại chứ không phải bởi bất kỳ đứa con nào của nó.

Cập nhật

Về nội dung của các tệp trong `$ fpath ':

Với tính năng tự động tải kiểu zsh, nội dung của tệp là phần thân của hàm được xác định. Do đó, một tệp có tên "hello" chứa một dòng echo "Hello world."hoàn toàn xác định một chức năng gọi là "xin chào". Bạn có thể tự do đặt hello () { ... }mã, nhưng điều đó sẽ không cần thiết.

Tuy nhiên, tuyên bố rằng một tệp chỉ có thể chứa một chức năng không hoàn toàn chính xác.

Đặc biệt nếu bạn xem xét một số chức năng từ hệ thống hoàn thành dựa trên chức năng (compsys), bạn sẽ nhanh chóng nhận ra đó là một quan niệm sai lầm. Bạn có thể tự do xác định các chức năng bổ sung trong một tệp chức năng. Bạn cũng có thể tự do thực hiện bất kỳ loại khởi tạo nào, mà bạn có thể cần thực hiện lần đầu tiên khi hàm được gọi. Tuy nhiên, khi bạn làm, bạn sẽ luôn xác định một hàm có tên giống như tệp trong tệp và gọi hàm đó ở cuối tệp, vì vậy nó sẽ được chạy lần đầu tiên khi hàm được tham chiếu.

Nếu - với các hàm phụ - bạn không xác định hàm có tên như tệp trong tệp, bạn sẽ kết thúc với hàm đó có định nghĩa hàm trong đó (cụ thể là các hàm phụ trong tệp). Bạn thực sự sẽ xác định tất cả các chức năng phụ của mình mỗi khi bạn gọi hàm được đặt tên giống như tệp. Thông thường, đó không phải là điều bạn muốn, vì vậy bạn sẽ xác định lại một hàm, được đặt tên giống như tệp trong tệp.

Tôi sẽ bao gồm một bộ xương ngắn, nó sẽ cho bạn ý tưởng về cách thức hoạt động của nó:

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

Nếu bạn chạy ví dụ ngớ ngẩn này, lần chạy đầu tiên sẽ như thế này:

xin chào
khởi tạo ...
Chào thế giới.

Và các cuộc gọi liên tiếp sẽ như thế này:

xin chào
Chào thế giới.

Tôi hy vọng điều này sẽ làm mọi thứ rõ ràng.

(Một trong những ví dụ thực tế phức tạp hơn sử dụng tất cả các thủ thuật đó là hàm ` _tmux ' đã được đề cập từ hệ thống hoàn thành dựa trên chức năng của zsh.)


Cảm ơn Frank! Tôi đọc trong các câu trả lời khác rằng tôi chỉ có thể xác định một chức năng cho mỗi tệp, đúng không? Tôi nhận thấy bạn đã không sử dụng cú pháp my_function () { }trong Hello worldví dụ của bạn . Nếu cú ​​pháp là không cần thiết, khi nào nó sẽ hữu ích để sử dụng nó?
Amelio Vazquez-Reina

1
Tôi mở rộng câu trả lời ban đầu để giải quyết những câu hỏi đó.
Frank Terbeck

Bạn sẽ luôn xác định hàm có tên giống như tệp trong tệp và gọi hàm đó ở cuối tệp. Tại sao vậy?
Hibou57

Hibou57: (Ngoài ra: Đó là một lỗi đánh máy ở đó, nên là "xác định hàm", hiện đã được sửa.) Tôi nghĩ điều đó đã rõ ràng, khi bạn xem xét đoạn mã sau đây. Dù sao, tôi đã thêm một đoạn đánh vần lý do theo nghĩa đen hơn một chút.
Frank Terbeck

Đây là thông tin thêm từ trang web của bạn, cảm ơn.
Timo

5

Tên của tệp trong thư mục được đặt tên bởi một thành fpathphần phải khớp với tên của chức năng tự động tải mà nó xác định.

Hàm của bạn được đặt tên my_function~/.my_zsh_functionslà thư mục dự định trong của bạn fpath, vì vậy định nghĩa của my_functionnên có trong tệp ~/.my_zsh_functions/my_function.

Số nhiều trong tên tệp được đề xuất của bạn ( functions_1) cho biết rằng bạn đang dự định đặt nhiều chức năng trong tệp. Đây không phải là cách fpathvà tự động tải công việc. Bạn nên có một định nghĩa hàm cho mỗi tệp.


2

đưa ra source ~/.my_zsh_functions/functions1thiết bị đầu cuối và đánh giá my_function, bây giờ bạn sẽ có thể gọi hàm


2
Cảm ơn, nhưng vai trò của FPATHautoloadsau đó là gì? Tại sao tôi cũng cần nguồn tập tin? Xem câu hỏi cập nhật của tôi.
Amelio Vazquez-Reina

1

Bạn có thể "tải" một tệp với tất cả các chức năng của mình trong $ ZDOTDIR / .zshrc như thế này:

source $ZDOTDIR/functions_file

Hoặc có thể sử dụng dấu chấm "." thay vì "nguồn".


1
Cảm ơn, nhưng vai trò của FPATHautoloadsau đó là gì? Tại sao tôi cũng cần nguồn tập tin? Xem câu hỏi cập nhật của tôi.
Amelio Vazquez-Reina

0

Tìm nguồn cung ứng chắc chắn không phải là phương pháp phù hợp, vì những gì bạn dường như muốn là có các chức năng khởi tạo lười biếng. Đó là những gì autoloaddành cho. Đây là cách bạn hoàn thành những gì bạn đang theo đuổi.

Trong của bạn ~/.my_zsh_functions, bạn nói rằng bạn muốn đặt một chức năng gọi là my_functionechos "xin chào thế giới". Nhưng, bạn bọc nó trong một cuộc gọi chức năng, đó không phải là cách nó hoạt động. Thay vào đó, bạn cần tạo một tệp gọi là ~/.my_zsh_functions/my_function. Trong đó, chỉ cần đặt echo "Hello world", không phải trong một trình bao bọc chức năng. Bạn cũng có thể làm một cái gì đó như thế này nếu bạn thực sự thích có trình bao bọc.

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

Tiếp theo, trong .zshrctệp của bạn , thêm vào như sau:

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

Khi bạn tải một vỏ ZSH mới, hãy gõ which my_function. Bạn nên xem thứ này:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH đã loại bỏ my_feft cho bạn autoload -X. Bây giờ, chạy my_functionnhưng chỉ cần gõ my_function. Bạn sẽ thấy Hello worldin ra, và bây giờ khi bạn chạy, which my_functionbạn sẽ thấy chức năng được điền vào như thế này:

my_function () {
    echo "Hello world"
}

Bây giờ, phép màu thực sự xuất hiện khi bạn thiết lập toàn bộ ~/.my_zsh_functionsthư mục để làm việc autoload. Nếu bạn muốn mọi tệp bạn thả trong thư mục này hoạt động như thế này, hãy thay đổi những gì bạn đặt trong .zshrcđó thành thứ gì đó:

# 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.