Bash 函数装饰器

nfa*_*rar 15 bash function

在 python 中,我们可以使用自动应用和针对函数执行的代码来装饰函数。

bash 中是否有类似的功能?

在我目前正在处理的脚本中,我有一些样板文件来测试所需的参数并在它们不存在时退出 - 如果指定了调试标志,则显示一些消息。

不幸的是,我必须将这段代码重新插入到每个函数中,如果我想更改它,我将不得不修改每个函数。

有没有办法从每个函数中删除此代码并将其应用于所有函数,类似于 python 中的装饰器?

Sté*_*las 13

zsh有了匿名函数和带有函数代码的特殊关联数组,这会容易得多。随着bash但是你可以这样做:

decorate() {
  eval "
    _inner_$(typeset -f "$1")
    $1"'() {
      echo >&2 "Calling function '"$1"' with $# arguments"
      _inner_'"$1"' "$@"
      local ret=$?
      echo >&2 "Function '"$1"' returned with exit status $ret"
      return "$ret"
    }'
}

f() {
  echo test
  return 12
}
decorate f
f a b
Run Code Online (Sandbox Code Playgroud)

这将输出:

Calling function f with 2 arguments
test
Function f returned with exit status 12
Run Code Online (Sandbox Code Playgroud)

你不能调用装饰两次来装饰你的函数两次。

zsh

decorate()
  functions[$1]='
    echo >&2 "Calling function '$1' with $# arguments"
    () { '$functions[$1]'; } "$@"
    local ret=$?
    echo >&2 "function '$1' returned with status $ret"
    return $ret'
Run Code Online (Sandbox Code Playgroud)

  • 我不跟着你。我的回答是尝试作为 [我理解的 Python 装饰器](http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/#_9_decorators) 的近图 (2认同)

mik*_*erv 5

我之前已经多次讨论过以下方法的工作方式和原因,所以我不会再这样做了。就个人而言,我自己最喜欢的主题是这里这里

如果您对阅读这些内容不感兴趣,但仍然很好奇,只需了解附加到函数输入的 here-docs 会在函数运行之前针对 shell 扩展进行评估,并且它们是在定义函数时的状态下重新生成的每次调用该函数时。

宣布

您只需要一个声明其他函数的函数。

_fn_init() { . /dev/fd/4 ; } 4<<INIT
    ${1}() { $(shift ; printf %s\\n "$@")
     } 4<<-REQ 5<<-\\RESET
            : \${_if_unset?shell will ERR and print this to stderr}
            : \${common_param="REQ/RESET added to all funcs"}
        REQ
            _fn_init $(printf "'%s' " "$@")
        RESET
INIT
Run Code Online (Sandbox Code Playgroud)

运行

在这里,我要求_fn_init声明一个名为fn.

set -vx
_fn_init fn \
    'echo "this would be command 1"' \
    'echo "$common_param"'

#OUTPUT#
+ _fn_init fn 'echo "this would be command 1"' 'echo "$common_param"'
shift ; printf %s\\n "$@"
++ shift
++ printf '%s\n' 'echo "this would be command 1"' 'echo "$common_param"'
printf "'%s' " "$@"
++ printf ''\''%s'\'' ' fn 'echo "this would be command 1"' 'echo "$common_param"'
#ALL OF THE ABOVE OCCURS BEFORE _fn_init RUNS#
#FIRST AND ONLY COMMAND ACTUALLY IN FUNCTION BODY BELOW#
+ . /dev/fd/4

    #fn AFTER _fn_init .dot SOURCES IT#
    fn() { echo "this would be command 1"
        echo "$common_param"
    } 4<<-REQ 5<<-\RESET
            : ${_if_unset?shell will ERR and print this to stderr}
            : ${common_param="REQ/RESET added to all funcs"}
        REQ
            _fn_init 'fn' \
               'echo "this would be command 1"' \
               'echo "$common_param"'
        RESET
Run Code Online (Sandbox Code Playgroud)

必需的

如果我想调用这个函数,除非设置环境变量,否则它会死_if_unset

fn

#OUTPUT#
+ fn
/dev/fd/4: line 1: _if_unset: shell will ERR and print this to stderr
Run Code Online (Sandbox Code Playgroud)

请注意 shell 跟踪的顺序 - 不仅fn_if_unset未设置时调用时失败,而且它永远不会首先运行。这是在使用 here-document 扩展时要理解的最重要的因素 - 它们必须始终首先出现,因为它们<<input毕竟是。

错误来自于/dev/fd/4父 shell 在将其交给函数之前评估该输入。这是测试必要环境的最简单、最有效的方法。

无论如何,故障很容易补救。

_if_unset=set fn

#OUTPUT#
+ _if_unset=set
+ fn
+ echo 'this would be command 1'
this would be command 1
+ echo 'REQ/RESET added to all funcs'
REQ/RESET added to all funcs
Run Code Online (Sandbox Code Playgroud)

灵活的

common_param对于由 声明的每个函数,该变量在输入时被评估为默认值_fn_init。但是该值也可以更改为任何其他值,每个类似声明的函数也将遵守该值。我现在将离开外壳痕迹 - 我们不会在这里或任何未知领域进入任何未知领域。

set +vx
_fn_init 'fn' \
               'echo "Hi! I am the first function."' \
               'echo "$common_param"'
_fn_init 'fn2' \
               'echo "This is another function."' \
               'echo "$common_param"'
_if_unset=set ;
Run Code Online (Sandbox Code Playgroud)

上面我声明了两个函数和 set _if_unset。现在,在调用任一函数之前,我将取消设置,common_param以便您可以看到当我调用它们时它们会自行设置它。

unset common_param ; echo
fn ; echo
fn2 ; echo

#OUTPUT#
Hi! I am the first function.
REQ/RESET added to all funcs

This is another function.
REQ/RESET added to all funcs
Run Code Online (Sandbox Code Playgroud)

现在从调用者的范围:

echo $common_param

#OUTPUT#
REQ/RESET added to all funcs
Run Code Online (Sandbox Code Playgroud)

但现在我希望它完全是另一回事:

common_param="Our common parameter is now something else entirely."
fn ; echo 
fn2 ; echo

#OUTPUT#
Hi! I am the first function.
Our common parameter is now something else entirely.

This is another function.
Our common parameter is now something else entirely.
Run Code Online (Sandbox Code Playgroud)

如果我取消设置_if_unset

unset _if_unset ; echo
echo "fn:"
fn ; echo
echo "fn2:"
fn2 ; echo

#OUTPUT#
fn:
dash: 1: _if_unset: shell will ERR and print this to stderr

fn2:
dash: 1: _if_unset: shell will ERR and print this to stderr
Run Code Online (Sandbox Code Playgroud)

重启

如果您需要随时重置函数的状态,这很容易完成。你只需要做(从函数内):

. /dev/fd/5
Run Code Online (Sandbox Code Playgroud)

我在5<<\RESET输入文件描述符中保存了用于最初声明函数的参数。因此.dot,在任何时候在 shell 中采购它都会重复最初设置它的过程。如果您愿意忽略 POSIX 实际上并未指定文件描述符设备节点路径(这是 shell 的.dot.

您可以轻松扩展此行为并为您的函数配置不同的状态。

更多的?

顺便说一下,这几乎没有触及表面。我经常使用这些技术将可随时声明的小辅助函数嵌入到主函数的输入中 - 例如,$@根据需要添加额外的位置数组。事实上 - 正如我所相信的,无论如何,高阶 shell 必须非常接近于此。您可以看到它们很容易以编程方式命名。

我还想声明一个生成器函数,它接受有限类型的参数,然后沿着 lambda 的行定义一个一次性或其他范围限制的燃烧器函数 - 或内嵌函数 -unset -f当通过。您可以传递一个 shell 函数。