我很难在 zsh 中定义和运行我自己的 shell 函数。我按照官方文档上的说明进行操作,并首先尝试使用简单的示例,但未能使其正常工作。
我有一个文件夹:
~/.my_zsh_functions
Run Code Online (Sandbox Code Playgroud)
在这个文件夹中,我有一个functions_1具有rwx用户权限的文件。在这个文件中,我定义了以下 shell 函数:
my_function () {
echo "Hello world";
}
Run Code Online (Sandbox Code Playgroud)
我定义FPATH为包含文件夹的路径~/.my_zsh_functions:
export FPATH=~/.my_zsh_functions:$FPATH
Run Code Online (Sandbox Code Playgroud)
我可以.my_zsh_functions用echo $FPATH或确认文件夹在函数路径中echo $fpath
但是,如果我再从 shell 中尝试以下操作:
> autoload my_function
> my_function
Run Code Online (Sandbox Code Playgroud)
我得到:
zsh:my_test_function:未找到函数定义文件
我还需要做些什么才能打电话my_function吗?
到目前为止的答案建议使用 zsh 函数获取文件。这是有道理的,但我有点困惑。zsh 不应该知道这些文件在哪里FPATH吗?那这样做的目的是autoload什么?
Fra*_*eck 139
在 zsh 中,函数搜索路径($fpath)定义了一组目录,其中包含可以标记为在第一次需要它们包含的函数时自动加载的文件。
Zsh 有两种自动加载文件的模式:Zsh 的本地方式和另一种类似于 ksh 自动加载的模式。如果设置了 KSH_AUTOLOAD 选项,则后者处于活动状态。Zsh 的本机模式是默认模式,我不会在这里讨论其他方式(有关 ksh 式自动加载的详细信息,请参阅“man zshmisc”和“man zshoptions”)。
好的。假设你有一个目录 `~/.zfunc' 并且你希望它成为函数搜索路径的一部分,你可以这样做:
fpath=( ~/.zfunc "${fpath[@]}" )
Run Code Online (Sandbox Code Playgroud)
这会将您的私人目录添加到搜索路径的前面。如果您想使用自己的功能覆盖 zsh 安装中的功能(例如,当您想使用更新的完成功能,例如来自 zsh 的 CVS 存储库中的“_git”以及较旧的 shell 安装版本时),这一点很重要。
还值得注意的是,`$fpath' 中的目录不是递归搜索的。如果您希望递归搜索您的私有目录,则必须自己处理,如下所示(以下代码段需要设置“EXTENDED_GLOB”选项):
fpath=(
~/.zfuncs
~/.zfuncs/**/*~*/(CVS)#(/N)
"${fpath[@]}"
)
Run Code Online (Sandbox Code Playgroud)
对于未受过训练的人来说,它可能看起来很神秘,但它实际上只是将 `~/.zfunc' 下的所有目录添加到 `$fpath',同时忽略名为“CVS”的目录(这很有用,如果您打算结帐整个从 zsh 的 CVS 到您的私有搜索路径的函数树)。
假设您有一个包含以下行的文件“~/.zfunc/hello”:
printf 'Hello world.\n'
Run Code Online (Sandbox Code Playgroud)
您现在需要做的就是将函数标记为在第一次引用时自动加载:
autoload -Uz hello
Run Code Online (Sandbox Code Playgroud)
“-Uz 是关于什么的?”,你问?好吧,这只是一组选项,它们会导致“自动加载”做正确的事情,不管其他选项设置了什么。`U' 在函数被加载时禁用别名扩展,并且即使 `KSH_AUTOLOAD' 出于任何原因设置,`z' 也会强制 zsh 风格的自动加载。
处理完这些之后,您可以使用新的 `hello' 函数:
zsh% 你好 你好,世界。
关于采购这些文件的一句话:那是错误的。如果你想获取那个 `~/.zfunc/hello' 文件,它只会打印“Hello world”。一次。而已。不会定义任何函数。此外,这个想法是只在需要时加载函数的代码。在“自动加载”调用之后,不会读取函数的定义。该函数只是标记为稍后根据需要自动加载。
最后,关于 $FPATH 和 $fpath 的说明:Zsh 将它们作为链接参数进行维护。小写参数是一个数组。大写版本是一个字符串标量,它包含链接数组中的条目,条目之间用冒号连接。这样做是因为使用数组处理标量列表更自然,同时还保持使用标量参数的代码的向后兼容性。如果您选择使用 $FPATH(标量),您需要小心:
FPATH=~/.zfunc:$FPATH
Run Code Online (Sandbox Code Playgroud)
会起作用,而以下不会:
FPATH="~/.zfunc:$FPATH"
Run Code Online (Sandbox Code Playgroud)
原因是双引号内不执行波浪号扩展。这很可能是您问题的根源。如果echo $FPATH打印波浪号而不是扩展路径,则它将不起作用。为了安全起见,我会使用 $HOME 而不是这样的波浪号:
FPATH="$HOME/.zfunc:$FPATH"
Run Code Online (Sandbox Code Playgroud)
话虽如此,我更愿意使用数组参数,就像我在本解释的顶部所做的那样。
您也不应该导出 $FPATH 参数。只有当前的 shell 进程需要它,它的任何子进程都不需要它。
关于`$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 "$@"
Run Code Online (Sandbox Code Playgroud)
如果你运行这个愚蠢的例子,第一次运行看起来像这样:
zsh% 你好 初始化... 你好,世界。
连续调用将如下所示:
zsh% 你好 你好,世界。
我希望这能解决问题。
(使用所有这些技巧的更复杂的现实世界示例之一是已经提到的来自 zsh 的基于函数的完成系统的“ _tmux ”函数。)
由fpath元素命名的目录中的文件名必须与其定义的可自动加载函数的名称相匹配。
你的函数被命名my_function并且~/.my_zsh_functions是你的预期目录fpath,所以 的定义my_function应该在文件中~/.my_zsh_functions/my_function。
您提议的文件名 ( functions_1) 中的复数表示您计划在文件中放置多个函数。这不是fpath自动加载的工作方式。每个文件应该有一个函数定义。
采购绝对不是正确的方法,因为您似乎想要的是具有延迟初始化的函数。这autoload就是为了。这就是你如何完成你所追求的。
在您的 中~/.my_zsh_functions,您说要放置一个名为my_function“hello world”的函数。但是,你将它包装在一个函数调用中,这不是它的工作原理。相反,您需要创建一个名为~/.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
Run Code Online (Sandbox Code Playgroud)
接下来,在您的.zshrc文件中,添加以下内容:
fpath=(~/.my_zsh_functions $fpath);
autoload -U ~/.my_zsh_functions/my_function
Run Code Online (Sandbox Code Playgroud)
当你加载一个新的 ZSH shell 时,输入which my_function. 你应该看到这个:
my_function () {
# undefined
builtin autoload -XU
}
Run Code Online (Sandbox Code Playgroud)
ZSH 刚刚用autoload -X. 现在,my_function只需键入my_function. 您应该会看到Hello world打印出来,现在当您运行时,which my_function您应该会看到如下填充的函数:
my_function () {
echo "Hello world"
}
Run Code Online (Sandbox Code Playgroud)
现在,当您设置整个~/.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)
Run Code Online (Sandbox Code Playgroud)