是否可以在 ZSH 中动态定义一个函数?

Is it possible to define a function dynamically in ZSH?

我想在ZSH中动态定义一系列函数

例如:

#!/bin/zsh
for action in status start stop restart; do
     $action() {
         systemctl $action $*
     }
done

但是,这会导致四个相同的函数都调用最后一个参数:

$ status libvirtd
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ====
Authentication is required to restart 'libvirtd.service'.
...

有没有办法像这样动态定义这些函数?

有可能,但是很丑:

for action in status start stop restart; do
    eval "$action() { systemctl $action \"$@\"; }"
done

与任何涉及 eval 的事情一样,这很难做到正确。 eval 所做的是解析命令两次,并在第二次解析时执行它。 "Huh?"我听到你说了吗?好吧,问题是函数定义中的 $variable 引用通常不会立即展开,而是在执行函数时展开。因此,当您的循环运行时(action 设置为 "status"):

$action() {
    systemctl $action $*
done

它扩展了对 $action 的第一个引用,但没有扩展第二个,给出:

status() {
    systemctl $action $*
done

相反,您希望 $action 的引用立即展开。但是您 想要立即扩展对 $* 的引用,因为那样它会使用脚本的参数,而不是在运行时提供给函数的参数。实际上,您根本不需要 $*,因为它在某些情况下会破坏参数;请改用 "$@"

因此,您需要一种方法来立即展开某些 variable/parameter 引用,并将某些引用推迟到以后。 eval 给了你。最棘手的事情是你可能需要两个级别的 quoting/escaping (一个用于第一次解析,一个用于第二次),你需要使用这些级别来控制哪些 variable/parameter 引用立即展开,后来。

运行时(action 设置为 "status"):

eval "$action() { systemctl $action \"$@\"; }"

...它执行解析过程,扩展未转义的变量引用并删除引用和转义级别,给出:

status() { systemctl status "$@"; }

...这就是你想要的。

是的,其实很简单:

for action in status start stop restart
do
    $action() {
        systemctl [=10=] "$@"
    }
done

这里的重点是[=12=]的使用。你原来的解决方案的问题是函数定义中的“$action”在定义过程中没有展开,所以在所有四个函数中它只引用了这个变量的最后一个值。因此,最好的解决方案不是使用 eval 来尝试使用丑陋的技巧(如另一个解决方案中所建议的那样),而是使用 $0... 在 shell 脚本中,$0 扩展为当前脚本的名称, 在 shell 函数中,它扩展到当前函数的名称。这恰好正是您想要的!

另请注意我是如何使用 "$@"(引号很重要)而不是 $*。这适用于带空格的引号参数,$* 废墟。

最后,对于这个用例,您可以使用 "alias" 而不是函数,一切都会简单得多:

for action in status start stop restart
do
    alias $action="systemctl $action"
done