在ZSH中是否可能动态定义函数?

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

有没有办法像这样动态定义这些函数呢?
2个回答

15

是的,实际上非常简单:

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

这里的关键点是使用$0。你原来解决方案的问题在于函数定义内部的"$action"没有在定义期间展开,因此在所有四个函数中,它只是引用了该变量的最后一个值。所以,不要尝试使用eval(另一个解决方案中建议的丑陋的诡计)使其工作,最好的解决方案就是使用$0... 在shell脚本中,$0扩展为当前脚本的名称,在shell函数中,它扩展为当前函数的名称。这恰好是你在这里想要的!

注意,我使用"$@"(引号很重要)而不是$*。这对带有空格的带引号参数可以正确工作,而$*则会破坏它们。

最后,对于这种用例,您可以使用"alias"代替函数,这样一切都会简单得多:

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

哈!如此简单,如此酷! - Alex Yu

1

这是可能的,但不太美观:

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

和任何涉及 eval 的事情一样,这都很难搞对。 eval 所做的是将命令解析两次,并在第二次解析时执行它。你说"什么?"?嗯,问题在于通常函数定义中的 $variable 引用不会立即扩展,而是在函数执行时才扩展。因此,当您的循环运行以下操作(并将 action 设置为 "status"):

$action() {
    systemctl $action $*
done

它扩展了对$action的第一个引用,但没有扩展第二个引用,因此得到如下结果:
status() {
    systemctl $action $*
done

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

因此,您需要一种方法来立即扩展一些变量/参数引用,并将一些延迟到以后。 eval可以实现这一点。最棘手的问题是您可能需要两个级别的引用/转义(一个用于第一次解析,一个用于第二次),并且您需要使用这些级别来控制哪些变量/参数引用立即展开,哪些稍后展开。

当这个代码段运行时(其中action设置为"status"):

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

...它进行解析处理,扩展未转义的变量引用并移除一层引号和转义字符,得到以下结果:

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

"...这就是你想要的。"

实际上,对于这个问题有一些不丑陋的解决方案 :-) 请查看我下面的答案。 - Nadav Har'El

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接