5

我想在 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'.
...

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

4

2 回答 2

12

是的,这实际上非常简单:

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

这里的重点是使用$0. 您原来的解决方案的问题是$action函数定义中的“”在定义期间没有展开,所以在所有四个函数中它只是引用了这个变量的最后一个值。因此,与其尝试使用 eval(如另一个解决方案中所建议的那样)让它与丑陋的诡计一起工作,最好的解决方案是使用 $0... 在 shell 脚本中,$0 扩展为当前脚本的名称,而在 shell函数,它扩展为当前函数的名称。这恰好是您想要的!

还要注意我是如何使用"$@"(引号很重要)而不是$*. 这适用于带空格的引用参数,这会$*破坏。

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

for action in status start stop restart
do
    alias $action="systemctl $action"
done
于 2019-06-11T16:16:46.767 回答
1

这是可能的,但很难看:

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

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

$action() {
    systemctl $action $*
done

它扩展了第一个引用$action而不是第二个引用,给出了这个:

status() {
    systemctl $action $*
done

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

所以你需要一种方法来立即扩展一些变量/参数引用,并将一些推迟到以后。eval给你。最棘手的事情是您可能需要两个级别的引用/转义(一个用于第一次解析传递,一个用于第二次解析),并且您需要使用这些级别来控制哪些变量/参数引用立即展开,哪些稍后展开。

当它运行时(action设置为“状态”):

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

...它进行解析传递,扩展未转义的变量引用并删除一定程度的引用和转义,给出:

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

...这就是你想要的。

于 2019-06-11T00:29:57.867 回答