Можно ли динамически определять функцию в ZSH? - PullRequest
1 голос
/ 11 июня 2019

Я хотел бы динамически определить ряд функций в 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 ]

2 голосов
/ 11 июня 2019

Да, это на самом деле очень просто:

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

Ключевым моментом здесь является использование $0. Проблема с вашим исходным решением состояла в том, что «$action» внутри определения функции не было расширено во время определения, поэтому во всех четырех функциях оно просто ссылалось на последнее значение этой переменной. Таким образом, вместо того, чтобы пытаться заставить его работать с уродливым обманом с помощью eval (как это предлагается в другом решении), самым хорошим решением будет просто использовать $ 0 ... В сценарии оболочки $ 0 расширяется до имени текущего сценария, а в оболочке функции, это распространяется на имя текущей функции. Что именно то, что вы хотели здесь!

Обратите внимание, как я использовал "$@" (цитаты важны) вместо $*. Это работает правильно с кавычками аргументов с пробелами, которые $* руины.

Наконец, для этого варианта использования вы могли бы использовать «псевдоним» вместо функции, и все было бы намного проще:

for action in status start stop restart
do
    alias $action="systemctl $action"
done
1 голос
/ 11 июня 2019

Это возможно, но безобразно:

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, установленным в «status»):

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

... он выполняет парсинг, расширяет ссылки на неэкранированные переменные и удаляет уровень цитированияи убегая, давая это:

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

... что вы и хотели.

...