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


54

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

我有一个文件夹:

~/.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 2012年

2
$ ZDOTDIR的值与此问题无关。该变量定义zsh在哪里寻找用户的配置文件。如果未设置,则使用$ HOME,这几乎是每个人的正确值。
弗兰克·特贝克

Answers:


94

在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”会在函数加载时禁用别名扩展,即使由于任何原因设置了“ KSH_AUTOLOAD”,“ z”也会强制执行zsh样式的自动加载。

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

zsh%你好
你好,世界。

关于采购这些文件的一句话:这是错误的。如果您要获取`〜/ .zfunc / hello'文件,它将仅显示“ Hello world”。一旦。而已。不会定义任何功能。此外,其想法是仅在需要时加载函数的代码。调用“ autoload”后,不会读取该函数的定义。该功能只是标记为以后需要时自动加载。

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

FPATH=~/.zfunc:$FPATH

将起作用,而以下情况将不起作用:

FPATH="~/.zfunc:$FPATH"

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

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

话虽如此,我宁愿像在此解释顶部一样使用array参数。

您也不应导出$ FPATH参数。当前外壳程序只需要它,而它的任何子进程都不需要。

更新资料

关于$ 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%你好
你好,世界。

我希望这可以解决问题。

(使用所有这些技巧的更复杂的现实示例之一是zsh基于函数的完成系统中已经提到的` _tmux函数。)


谢谢弗兰克!我在其他答案中读到,每个文件只能定义一个功能,对吗?我注意到您my_function () { }Hello world示例中没有使用语法。如果不需要语法,什么时候使用它会有用?
Amelio Vazquez-Reina 2012年

1
我也将原始答案扩展为解决这些问题。
Frank Terbeck 2012年

“您将始终在函数中定义类似于文件中的文件的名称,并在文件末尾调用该函数”:为什么这样?
Hibou57 2014年

Hibou57 :(此外:那是一个错字,应该是“定义一个函数”,现在已经修复。)当您考虑以下代码片段时,我认为这很清楚。无论如何,我添加了一段,从字面上更清楚地说明了原因。
Frank Terbeck 2014年

是您网站的更多信息,谢谢。
Timo

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
谢谢,但什么是角色FPATHautoload呢?为什么还需要源文件?看到我更新的问题。
Amelio Vazquez-Reina'3

1

您可以在$ ZDOTDIR / .zshrc中将具有所有功能的文件“加载”如下:

source $ZDOTDIR/functions_file

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


1
谢谢,但什么是角色FPATHautoload呢?为什么还需要源文件?看到我更新的问题。
Amelio Vazquez-Reina'3

0

采购绝对不是正确的方法,因为您似乎想要的是拥有懒惰的初始化函数。那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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.