是否可以在 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
我想在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