代码之家  ›  专栏  ›  技术社区  ›  Carl Meyer

如何重命名bash函数?

  •  27
  • Carl Meyer  · 技术社区  · 17 年前

    我正在围绕另一个定义bash函数的软件包开发一些方便的包装。我想用我自己的同名函数替换它们的bash函数,同时仍然能够从我的函数中运行它们的函数。换句话说,我需要重命名它们的函数,或者为其创建某种持久化别名,在创建同名函数时不会对其进行修改。

    $ theirfunc() { echo "do their thing"; }
    $ _orig_theirfunc() { theirfunc; }
    $ theirfunc() { echo "do my thing"; _orig_theirfunc }
    $ theirfunc
    do my thing
    do my thing
    do my thing
    ...
    

    显然我不想要无限递归,我想要:

    do my thing
    do their thing
    

    我该怎么做?

    8 回复  |  直到 17 年前
        1
  •  43
  •   Evan Broder    17 年前

    以下是消除临时文件的方法:

    $ theirfunc() { echo "do their thing"; }
    $ eval "$(echo "orig_theirfunc()"; declare -f theirfunc | tail -n +2)"
    $ theirfunc() { echo "do my thing"; orig_theirfunc; }
    $ theirfunc
    do my thing
    do their thing
    
        2
  •  18
  •   dimo414    7 年前

    进一步高呼 copy_function 和 rename_function 职能:

    copy_function() {
      test -n "$(declare -f "$1")" || return 
      eval "${_/$1/$2}"
    }
    
    rename_function() {
      copy_function "$@" || return
      unset -f "$1"
    }
    

    从@Dmitri Rubinstein的解决方案开始:

    • 不用打电话了 declare
    • 消除温度变量( func )通过使用 _ 特殊变量。
      • test -n ... 这是我能想出的唯一办法 _
    • 改变 return 1 到 return (返回当前状态代码)
    • 使用模式替换而不是前缀删除。

    一旦 复制功能 重命名函数 不重要的(只是不要重命名 复制功能 ;-)

        3
  •  13
  •   Carl Meyer    17 年前

    啊哈。找到了一个解决方案,尽管它不是很漂亮:

    $ theirfunc() { echo "do their thing"; }
    $ echo "orig_theirfunc()" > tmpfile
    $ declare -f theirfunc | tail -n +2 >> tmpfile
    $ source tmpfile
    $ theirfunc() { echo "do my thing"; orig_theirfunc; }
    $ theirfunc
    do my thing
    do their thing
    

    我相信这可以通过一个真正的bash向导来改进。特别是,最好不要再需要临时文件了。

    :bash向导埃文·布罗德奋起迎接挑战(见上文接受的答案)。我将他的答案重新表述为一个通用的“复制函数”:

    # copies function named $1 to name $2
    copy_function() {
        declare -F $1 > /dev/null || return 1
        eval "$(echo "${2}()"; declare -f ${1} | tail -n +2)"
    }
    

    可以这样使用:

    $ theirfunc() { echo "do their thing"; }
    $ copy_function theirfunc orig_theirfunc
    $ theirfunc() { echo "do my thing"; orig_theirfunc; }
    $ theirfunc
    do my thing
    do their thing
    

    很不错的!

        4
  •  8
  •   parched    11 年前

    如果你只是想在名字前面加上什么,说 orig_

    eval orig_"$(declare -f theirfun)"
    
        5
  •  5
  •   Dmitri Rubinstein    14 年前

    通过使用shell参数展开而不是tail命令,可以改进copy_函数:

    copy_function() {
      declare -F "$1" > /dev/null || return 1
      local func="$(declare -f "$1")"
      eval "${2}(${func#*\(}"
    }
    
        6
  •  4
  •   Community Mohan Dere    9 年前

    要总结所有其他解决方案并部分纠正它们,以下是解决方案:

    • 不使用 declare 两次
    • 不需要外部程序(如 tail )
    • 没有意外的替换
    • 相对较短
    • 由于正确的引用,保护您免受常见编程错误的影响

    但是:

    • 它可能不适用于递归函数,因为在副本中用于递归的函数名没有被替换。让这样的替代品正确使用是一项非常复杂的任务。如果你想用这样的替代品,你可以试试这个答案 https://stackoverflow.com/a/18839557 何处 eval "${_//$1/$2}" 而不是 eval "${_/$1/$2}" (请注意双精度。) // a )它在计算递归时失败(如 command_help() { case "$1" in ''|-help) echo "help [command]"; return;; esac; "command_$1" -help "${@:2}"; } )

    一切结合起来:

    : rename_fn oldname newname
    rename_fn()
    {
      local a
      a="$(declare -f "$1")" &&
      eval "function $2 ${a#*"()"}" &&
      unset -f "$1";
    }
    

    现在测试:

    somefn() { echo one; }
    rename_fn somefn thatfn
    somefn() { echo two; }
    somefn
    thatfn
    

    所需的产出:

    two
    one
    

    现在,请尝试一些更复杂的案例,这些案例都给出了预期的结果或失败:

    rename_fn unknown "a b"; echo $?
    rename_fn "a b" murx; echo $?
    
    a(){ echo HW; }; rename_fn " a " b; echo $?; a
    a(){ echo "'HW'"; }; rename_fn a b; echo $?; b
    a(){ echo '"HW"'; }; rename_fn a b; echo $?; b
    a(){ echo '"HW"'; }; rename_fn a "b c"; echo $?; a
    

    有人可能会说,以下仍然是一个bug:

    a(){ echo HW; }; rename_fn a " b "; echo $?; b
    

    " b " 不是正确的函数名。如果您确实需要此功能,则需要以下变体:

    rename_fn()
    {
      local a
      a="$(declare -f "$1")" &&
      eval "function $(printf %q "$2") ${a#*"()"}" &&
      unset -f "$1";
    }
    

    现在这也抓住了这个人工案例。(请注意 printf %q 是一个 bash 内置的。)

    当然,您可以将其拆分为复制+重命名,如下所示:

    copy_fn() { local a; a="$(declare -f "$1")" && eval "function $(printf %q "$2") ${a#*"()"}"; }
    rename_fn() { copy_fn "$@" && unset -f "$1"; }
    

    我希望这是101%的解决方案。如果需要改进,请评论;)

        7
  •  2
  •   Gareth Stockwell    14 年前

    以下是一个基于@Evan Broder方法的函数:

    # Syntax: rename_function <old_name> <new_name>
    function rename_function()
    {
        local old_name=$1
        local new_name=$2
        eval "$(echo "${new_name}()"; declare -f ${old_name} | tail -n +2)"
        unset -f ${old_name}
    }
    

    一旦定义了它,您就可以简单地执行 rename_function func orig_func

    # Syntax: prepend_to_function <name> [statements...]
    function prepend_to_function()
    {
        local name=$1
        shift
        local body="$@"
        eval "$(echo "${name}(){"; echo ${body}; declare -f ${name} | tail -n +3)"
    }
    
    # Syntax: append_to_function <name> [statements...]
    function append_to_function()
    {
        local name=$1
        shift
        local body="$@"
        eval "$(declare -f ${name} | head -n -1; echo ${body}; echo '}')"
    }
    

    一旦定义了这些函数,假设您有一个现有函数,如下所示:

    function foo()
    {
        echo stuff
    }
    

    然后你可以做:

    prepend_to_function foo echo before
    append_to_function foo echo after
    

    使用 declare -f foo ,我们可以看到效果:

    foo ()
    {
        echo before;
        echo stuff;
        echo after
    }
    
        8
  •  2
  •   Andy    8 年前

    对于我们这些被迫与bash 3.2兼容的人(你知道我们在谈论谁), declare -f 不起作用。我发现 type 能行

    eval "$(type my_func | sed $'1d;2c\\\nmy_func_copy()\n')"
    

    在函数形式中,它看起来像

    copy_function()
    {
      eval "$(type "${1}"| sed $'1d;2c\\\n'"${2}"$'()\n')"
    }
    

    如果你真的不想依赖 sed ...

    function copy_function()
    {
      eval "$({
      IFS='' read -r line
      IFS='' read -r line
      echo "${2} ()"
      while IFS='' read -r line || [[ -n "$line" ]]; do
        echo "$line"
      done
      }< <(type "${1}"))"
    }
    

    但这对我来说有点罗嗦

        9
  •  0
  •   Maëlan    7 年前

    我知道这是一个老问题,但是还没有人用递归来解决这个问题。

    有一种干净的方法可以复制递归函数,它依赖于Bash的一个模糊的角落。事实上,它是如此晦涩难懂,以至于我对它的申请感到惊讶。就这样。

    但是,正如下面解释的,这个技巧不足以处理所有递归函数。这就是为什么我还提出了另一个解决方案,它可能更直接,但也更昂贵。

    使用别名的部分解决方案

    解释

    从…起 man bash ,节别名(重点):

    令人困惑Bash始终至少读取一行完整的输入, 以及组成复合命令的所有行,然后再执行 别名是 在读取命令时展开,而不是在执行命令时展开。 别名定义与另一个命令显示在同一行上 在读取下一行输入之前不会生效。命令 新别名。当函数为空时,此行为也是一个问题 执行。 当函数被执行时不是 ,因为函数定义是 它本身就是一个命令。因此,函数中定义的别名是 在执行该功能之前不可用。为了安全起见, 始终将别名定义放在单独的行上,并且不要使用别名 在复合命令中。

    否则,当定义一个函数时,该函数体中出现的所有别名 那个 时间延长了。(相反,调用函数时不会发生别名扩展。)可以利用这一点来替代函数体内部的递归调用,而无需诉诸肮脏、不可靠的方法 sed 调用。

    declare -fp $old_name . 然后将函数的新名称放在这个主体的顶部,而不是旧名称(使用Bashs变量替换机制),并将整个内容提供给 eval

    密码

    下面给出的代码是本着 @ingidotnet’s excellent answer ,增加了对递归函数的支持。

    • 它只使用shell内置程序,没有外部程序(例如 tail 塞德 ).
    • 它不会执行不合理的文本替换(除了关于 declare -fp ).
    • 它被正确地引用了。
    • 它支持复制 一些 递归函数。

    $(old_name ...) .

    function copy_function() {
        declare old="$1"
        declare new="$2"
        # input checks:
        if [[ ! "$old" =~ ^[a-zA-Z0-9._-]+$ ]] ; then
            printf >&2 'copy_function: %q is (probably) not a valid function name\n' "$old"
            return 1
        elif [[ ! "$new" =~ ^[a-zA-Z0-9._-]+$ ]] ; then
            printf >&2 'copy_function: %q is (probably) not a valid function name\n' "$new"
            return 1
        fi
        # find the definition of the existing function:
        declare def ; def="$(declare -fp "$old")" || return
        # create an alias, in order to substitute $old for $new in function body:
        declare former_alias="$(alias "$old" 2>/dev/null)"
        alias "$old=$new"
        # define the function $new:
        eval "${def/#$old ()/$new ()}"
        # remove the alias, restoring the former one if needed:
        unalias "$old"
        [ -n "$former_alias" ] && eval "$former_alias" || true
    }
    
    rename_function() {
        copy_function "$@" || return
        unset -f "$1"
    }
    

    例1

    以下代码:

    # a recursive function which prints a range of numbers
    function enum() {
        declare -i i="$1"
        declare -i j="$2"
        if [ $i -gt $j ] ; then
            return
        elif [ $i -eq $j ] ; then
            echo $i
        else
            declare -i k=$(( i + j ))
            [ $k -lt 0 ] && k=$(( k-1 ))
            k=$(( k / 2 ))
            enum $i $k
            enum $(( k+1 )) $j
        fi
    }
    rename_function enum range
    declare -fp enum range
    range 1 5
    

    将按预期工作(使用bash 5.0.7进行测试):

    bash: declare: enum: not found
    range () 
    { 
        declare -i i="$1";
        declare -i j="$2";
        if [ $i -gt $j ]; then
            return;
        else
            if [ $i -eq $j ]; then
                echo $i;
            else
                declare -i k=$(( i + j ));
                [ $k -lt 0 ] && k=$(( k-1 ));
                k=$(( k / 2 ));
                range $i $k;
                range $((k+1)) $j;
            fi;
        fi
    }
    
    1
    2
    3
    4
    5
    

    例2

    但是,以下递归函数不能正确重命名。

    # the Fibonacci function
    function fib() {
        declare -i n="$1"
        if [ $n -le 1 ] ; then
            echo $n
        else
            declare -i x=$(fib $(( n-2 )))
            declare -i y=$(fib $(( n-1 )))
            echo $(( x + y ))
        fi
    }
    rename_function fib FIB
    declare -fp fib FIB
    FIB 5
    

    输出为:

    bash: declare: fib: not found
    FIB () 
    { 
        declare -i n="$1";
        if [ $n -le 1 ]; then
            echo $n;
        else
            declare -i x=$(fib $(( n-2 )));
            declare -i y=$(fib $(( n-1 )));
            echo $(( x + y ));
        fi
    }
    
    bash: fib: command not found
    bash: fib: command not found
    0
    

    这里有另一种方法。只需将新函数定义为包装器函数,包装器函数在本地重新定义原始函数并调用它。

    与别名技巧相比,这可以处理所有递归调用,但成本要高得多,因为在每次调用新函数时都会重新定义和恢复原始函数。

    下面是与该想法相对应的代码。据我所知,它没有剩余的缺陷。

    function copy_function() {
        declare old="$1"
        declare new="$2"
        # input checks:
        if [[ ! "$old" =~ ^[a-zA-Z0-9._-]+$ ]] ; then
            printf >&2 'copy_function: %q is (probably) not a valid function name\n' "$old"
            return 1
        elif [[ ! "$new" =~ ^[a-zA-Z0-9._-]+$ ]] ; then
            printf >&2 'copy_function: %q is (probably) not a valid function name\n' "$new"
            return 1
        fi
        # find the definition of the existing function:
        declare def ; def="$(declare -fp "$old")" || return
        # define the new function as a wrapper around the old function:
        eval "$(printf '
                function %s() {
                    # save the current function $old, if any:
                    declare former_def="$(declare -fp %s 2>/dev/null)"
                    # re-define the original function $old:
                    %s
                    # call the original function $old:
                    %s "$@"
                    # restore the current function $old, if any:
                    declare -i ret=$?
                    if [ -z "$former_def" ] ; then
                        unset -f %s
                    else
                        eval "$former_def"
                    fi
                    return $ret
                }
            ' "$new" "$old" "$def" "$old" "$old"
        )"
    }
    

    例2

    这一次,上面的示例2按预期工作:

    bash: declare: fib: not found
    FIB () 
    { 
        declare former_def="$(declare -fp fib 2>/dev/null)";
        function fib () 
        { 
            declare -i n="$1";
            if [ $n -le 1 ]; then
                echo $n;
            else
                declare -i x=$(fib $(( n-2 )));
                declare -i y=$(fib $(( n-1 )));
                echo $(( x + y ));
            fi
        };
        fib "$@";
        declare -i ret=$?;
        if [ -z "$former_def" ]; then
            unset -f fib;
        else
            eval "$former_def";
        fi;
        return $ret
    }
    
    55
    
    推荐文章