我想在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'.
...
有没有办法像这样动态定义这些功能?
答案 0 :(得分:2)
是的,实际上很简单:
for action in status start stop restart
do
$action() {
systemctl $0 "$@"
}
done
此处的重点是使用$0
。原始解决方案的问题在于,在定义期间未扩展函数定义内的“ $action
”,因此在所有四个函数中,它仅引用此变量的最后一个值。因此,最好的解决方案是使用$ 0 ...,而不是尝试使用eval使其与丑陋的骗局一起使用(如另一种解决方案中所建议的),在shell脚本中,$ 0扩展为当前脚本的名称,并在shell中函数,它将扩展为当前函数的名称。碰巧正是您想要的!
还要注意我是如何使用"$@"
(而不是引号)来代替$*
的。这可以正确处理带引号的带空格的引号,该引号$*
会毁了。
最后,在本用例中,您可以使用“别名”代替功能,并且一切都将更加简单:
for action in status start stop restart
do
alias $action="systemctl $action"
done
答案 1 :(得分:1)
有可能,但是很丑:
for action in status start stop restart; do
eval "$action() { systemctl $action \"\$@\"; }"
done
与涉及eval
的任何事物一样,要正确就很难了。 eval
所做的事情是两次解析命令,然后在第二次解析中执行它。 “ Hu?”我听到你说吗好吧,事情是,通常在函数定义中的$variable
引用不会立即扩展,而是在执行函数时立即扩展。因此,当您的循环运行时(action
设置为“状态”):
$action() {
systemctl $action $*
done
它将第一个引用扩展到$action
,但没有扩展第二个引用,给出以下内容:
status() {
systemctl $action $*
done
相反,您要同时扩展对$action
的引用。但是,您不希望立即扩展对$*
的引用,因为那样的话,它将使用脚本的参数,而不是运行时赋予函数的参数。实际上,您根本不需要$*
,因为它在某些情况下会破坏参数。改用"$@"
。
因此,您需要一种方法来立即扩展某些变量/参数引用,并将其推迟到以后。 eval
给了你。棘手的大事情是,您可能需要两个级别的引用/转义(一个用于第一遍解析,第二个遍历),并且您需要使用这些级别来控制哪些变量/参数引用立即扩展,以及稍后扩展。
运行时(action
设置为“状态”):
eval "$action() { systemctl $action \"\$@\"; }"
...执行解析,扩展未转义的变量引用并删除引号和转义级别,给出以下内容:
status() { systemctl status "$@"; }
...这就是您想要的。