Search code examples
functionmetaprogrammingzshfunction-definition

Is it possible to define a function dynamically in ZSH?


I would like to define a series of functions dynamically in ZSH.

For example:

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

However, this results in four identical functions which all call the final argument:

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

Is there any way to define these functions dynamically like this?


Solution

  • Yes, it's actually very easy:

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

    The key point here is the use of $0. The problem with your original solution was that the "$action" inside the function's definition was not expanded during the definition, so in all four functions it just referred to the last value of this variable. So instead of trying to get it to work with ugly trickery using eval (as suggested in another solution), the nicest solution is just to use $0... In shell script, $0 expands to the name of the current script, and in shell functions, it expends to the name of the current function. Which happens to be exactly what you wanted here!

    Note also how I used "$@" (the quotes are important) instead of $*. This works correctly with quoted arguments with whitespace, which $* ruins.

    Finally, for this use case you could have used "alias" instead of function, and everything would have been much simpler:

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