zsh 有类似闭包的东西吗?

zsh 有类似闭包的东西吗?

我刚刚决定尝试 zsh (通过 oh-my-zsh),现在正在尝试precmd模拟一个两行提示,该提示不仅仅在最后一行中有正确的提示。

所以我克隆了默认主题,并受到启发这个帖子(我也用它来学习很多东西),我做了这样的事情(稍后我会添加颜色):

function precmd {
    local cwd="${(%):-[%~]}"
    local who_where="${(%):-%n@%m}"
    local git_info=${(%)$(git_prompt_info)}
    local right_prompt="     $git_info [$who_where]"
    local left_prompt="${(r:(($COLUMNS - ${#${right_prompt}})):: :)cwd}"

    echo "$left_prompt$right_prompt"
}

它有效。但我忍不住想知道:zsh 是否在每次调用 precmd 时定义所有这些变量?

我一直在谷歌搜索与 zsh 相关的闭包、作用域和命名空间,希望将本地变量作为数据附加到 precmd,因此不需要每次都重新定义变量,但我什么也没找到。有什么方法可以做我正在尝试的事情,还是我应该放弃它?

作为旁注,并且仅当相关时,“加载函数”是什么意思?

答案1

Zsh 没有诸如闭包、包或命名空间之类的东西。 Zsh 缺少一些实现真正的闭包所需的东西:

  • 功能不是一流的。您不能将函数作为参数传递给其他函数,并且函数不能返回其他函数。 (您可以通过姓名要调用的函数的名称,但这与传递函数本身不同)。

  • 不能有嵌套函数。 zsh 中的所有函数都是全局的。您必须为函数名称添加前缀以避免冲突。特别注意,函数将隐藏同名的外部程序。如果您有一个名为 的函数ls,则将调用该函数而不是程序ls。这可能很有用,除非您不小心这样做了。

  • 变量的作用域是动态的,而不是像大多数现代语言那样是静态的。即使您可以有嵌套函数,内部函数也不会按照您通常期望的方式关闭外部函数的局部变量。你不能像人们用 Javascript 那样使用它们来制作模块。

  • 兹什有匿名函数,但如果没有任何其他东西,它们就没有多大用处。

所以基本上,你能做的最好的事情就是为所有函数和全局变量添加前缀。

我还要指出你应该precmd像这样定义你的:

% autoload -Uz add-zsh-hook
% add-zsh-hook precmd my_precmd_function

add-zsh-hook让您挂钩您的函数,precmd而不会覆盖任何其他可能也想挂钩的函数precmd

加载函数意味着什么是一个单独的问题。 Zsh 具有自动加载功能,仅在实际调用函数时才从磁盘加载函数。当您这样做时,就可以调用autoload -Uz foobar名为的函数。foobar当您实际调用 时foobar,会从磁盘加载定义。

答案2

不,闭包对于 zsh 来说太复杂了。 Zsh 旨在解释与直接交互相距不远的小脚本。它不具备对于大型编程非常有用的花哨语言功能,但对于 shell 通常用于执行的小型任务则不太有用。

请注意,如果存在某种形式的闭包,允许一次一劳永逸地预先计算变量的值然后存储,那么当某些变化导致信息变得无效时,这些值将不会被更新。

$git_info由于对签入 git 或 git 存储库的文件进行修改,派生变量可以随时更改。所以无论如何它们每次都需要重新计算。

cwd您可以将和的值缓存who_where在全局变量中,因为它们在正常操作下不会改变。cwd当前目录更改时会更改,因此需要从chpwd.然而,这些变量的计算速度非常快,因此没有必要费心。这里昂贵的计算正在运行git_prompt_info,并且可能随时改变。

当您在每个命令之间显示信息时,最好将其作为提示(PS1psvar数组)的一部分。 Zsh 知道它必须在各种情况下重新显示提示,但它对您打印的内容一无所知precmd

答案3

是的,每次调用该函数时都会(重新)定义这些变量。

如果您只想初始化它们一次,您可以简单地将它们移至函数的顶层。

答案4

为了实现闭包,语言需要能够将函数作为元素或对象进行操作,这在 zsh 中是不可能的,除非通过字符串和内置函数eval(就像在其他 shell 中一样)。但这是非常有限的,因为您需要自己处理所有低级的东西(例如引用)。然而,当参数没有特殊字符时(因此不需要处理引用),使用这种想法很容易做一些简单的事情,并且在 zsh 中,匿名函数可以提供一点帮助。例如,要定义计算 的函数a*x+b*y,其中ab是在函数定义时提供的常量,x并且y是函数的参数:

mk_ax_plus_by() { echo "() { echo \$((($1)*(\$1)+($2)*(\$2))) }" }

fct_2x_plus_3y=$(mk_ax_plus_by 2 3)
fct_5x_plus_7y=$(mk_ax_plus_by 5 7)

因此,有一个fct_2x_plus_3y计算函数2*x+3*y和一个fct_5x_plus_7y计算函数5*x+7*y(请注意,我选择函数名称只是为了便于阅读,这些名称可以是任何名称,您甚至不需要将内容存储在变量中)。另请注意,这些实际上是字符串(不是 shell 术语中的函数),但它们的行为类似于eval内置函数。使用示例:

% eval $fct_2x_plus_3y 4 9
35
% eval $fct_5x_plus_7y 4 9
83

2*4+3*9给出 35 并给出5*4+7*983。

请注意,与函数式语言不同,这里需要区分来自环境的参数(未加引号)和定义函数的参数(加引号),即仅在函数调用时评估。但是可以将实现更改为更像函数式语言,并且用法将是相同的:

mk_ax_plus_by()
{
  local x='$1' y='$2'
  echo "() { echo \$((($1)*($x)+($2)*($y))) }"
}

相关内容