如何在 zsh 中定义并加载自己的 shell 函数

如何在 zsh 中定义并加载自己的 shell 函数

我很难在 zsh 中定义和运行我自己的 shell 函数。我跟着说明在官方文档上并首先尝试使用简单的示例,但我未能使其工作。

我有一个文件夹:

~/.my_zsh_functions

functions_1在这个文件夹中,我有一个名为“具有用户权限”的文件rwx。在此文件中,我定义了以下 shell 函数:

my_function () {
echo "Hello world";
}

我定义FPATH为包含文件夹的路径~/.my_zsh_functions

export FPATH=~/.my_zsh_functions:$FPATH

我可以使用或确认该文件夹.my_zsh_functions位于函数路径中echo $FPATHecho $fpath

但是,如果我随后从 shell 中尝试以下操作:

> autoload my_function
> my_function

我得到:

zsh: my_test_function: 找不到函数定义文件

我还需要做什么才能打电话吗my_function

更新:

到目前为止的答案建议使用 zsh 函数获取文件。这是有道理的,但我有点困惑。 zsh 不应该知道这些文件在哪里吗FPATH?那目的是什么autoload

答案1

在 zsh 中,函数搜索路径 ($fpath) 定义了一组目录,其中包含可以标记为在第一次需要它们包含的函数时自动加载的文件。

Zsh 有两种自动加载文件的模式:Zsh 的本机方式和另一种类似于 ksh 自动加载的模式。如果设置了 KSH_AUTOLOAD 选项,则后者处于活动状态。 Zsh 的本机模式是默认的,我不会在这里讨论其他方式(有关 ksh 式自动加载的详细信息,请参阅“man zshmisc”和“man zshoptions”)。

好的。假设你有一个目录“~/.zfunc”并且你希望它成为函数搜索路径的一部分,你可以这样做:

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

这会将您的私人目录添加到正面的搜索路径。如果您想用自己的安装覆盖 zsh 安装中的功能(例如,当您想使用更新的完成功能,例如 zsh 的 CVS 存储库中的“_git”与较旧安装的 shell 版本),这一点很重要。

还值得注意的是,“$fpath”中的目录不是递归搜索的。如果您希望递归搜索您的私有目录,您必须自己处理,如下所示(以下代码片段需要设置“EXTENDED_GLOB”选项):

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

对于未经训练的人来说,它可能看起来很神秘,但它实际上只是将 `~/.zfunc' 下面的所有目录添加到 `$fpath',同时忽略名为“CVS”的目录(如果您打算签出整个目录,这很有用)将 zsh 的 CVS 中的函数树添加到您的私人搜索路径中)。

假设您有一个文件“~/.zfunc/hello”,其中包含以下行:

printf 'Hello world.\n'

你现在需要做的就是标记首次引用时自动加载的函数:

autoload -Uz hello

您可能会问:“-Uz 是什么意思?”好吧,这只是一组选项,无论设置了什么选项,它们都会让“自动加载”做正确的事情。加载函数时,“U”会禁用别名扩展,“z”会强制 zsh 样式的自动加载,即使出于某种原因设置了“KSH_AUTOLOAD”。

处理完之后,您可以使用新的“hello”函数:

zsh% 你好
你好世界。

关于获取这些文件的一句话:那就是错误的。如果您获取“~/.zfunc/hello”文件,它只会打印“Hello world”。一次。而已。不会定义任何函数。此外,这个想法是只在函数的代码被调用时加载它。必需的。在“autoload”调用之后,函数的定义是不是读。该函数只是被标记为稍后根据需要自动加载。

最后,关于 $FPATH 和 $fpath 的注释:Zsh 将它们维护为链接参数。小写参数是一个数组。大写版本是一​​个字符串标量,包含链接数组中的条目,条目之间用冒号连接。这样做是因为使用数组处理标量列表更加自然,同时还保持了使用标量参数的代码的向后兼容性。如果您选择使用 $FPATH (标量),则需要小心:

FPATH=~/.zfunc:$FPATH

会起作用,而以下则不起作用:

FPATH="~/.zfunc:$FPATH"

原因是波浪号扩展不在双引号内执行。这可能是您问题的根源。如果echo $FPATH打印波形符而不是扩展路径,则它将不起作用。为了安全起见,我会使用 $HOME 而不是像这样的波形符:

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

话虽这么说,我更愿意使用数组参数,就像我在本解释顶部所做的那样。

您也不应该导出 $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 "$@"

如果您运行这个愚蠢的示例,第一次运行将如下所示:

zsh% 你好
正在初始化...
你好世界。

连续的调用将如下所示:

zsh% 你好
你好世界。

我希望这能解决问题。

(使用所有这些技巧的更复杂的现实世界示例之一是已经提到的`_tmux' 来自 zsh 基于函数的补全系统的函数。)

答案2

由元素命名的目录中的文件名fpath必须与其定义的自动加载函数的名称相匹配。

您的函数已命名my_function并且~/.my_zsh_functions是您的预期目录fpath,因此 的定义my_function应该在该文件中~/.my_zsh_functions/my_function

您建议的文件名中的复数 ( functions_1) 表示您计划在该文件中放置多个函数。这不是fpath自动加载的工作方式。每个文件应该有一个函数定义。

答案3

采购绝对不是正确的方法,因为您似乎想要的是具有延迟初始化的函数。这就是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

接下来,在您的.zshrc文件中添加以下内容:

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

当你加载新的 ZSH shell 时,输入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)

答案4

您可以在 $ZDOTDIR/.zshrc 中“加载”包含所有函数的文件,如下所示:

source $ZDOTDIR/functions_file

或者可以使用点“.”而不是“来源”。

相关内容