如何重命名bash函数?

时间:2009-07-29 23:07:54

标签: bash function

我正在围绕另一个定义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

我该怎么做?

9 个答案:

答案 0 :(得分:38)

这是一种消除临时文件的方法:

$ 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

答案 1 :(得分:13)

啊哈。找到了一个解决方案,虽然它不是真的很漂亮:

$ 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向导来改进。特别是放弃对tempfile的需求会很好。

更新:bash向导Evan Broder迎接挑战(见上面接受的答案)。我将他的答案重新制定为通用的“copy_function”函数:

# 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

非常好!

答案 2 :(得分:12)

进一步将copy_functionrename_function功能高尔夫球化为:

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

rename_function() {
  copy_function "$@" || return
  unset -f "$1"
}

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

  • 无需拨打declare两次。错误检查仍然有效。
  • 使用func特殊变量消除temp var(_)。
    • 注意:使用test -n ...是唯一可以保留_并且仍然能够在错误时返回的方法。
  • return 1更改为return(返回当前状态代码)
  • 使用模式替换而不是前缀删除。

定义copy_function后,它会使rename_function变得微不足道。 (只是不要重命名copy_function; - )

答案 3 :(得分:7)

如果您只想在名称前加上一些内容,请说orig_,那么我认为最简单的是

eval orig_"$(declare -f theirfun)"

答案 4 :(得分:5)

使用shell参数扩展而不是tail命令可以改进copy_function:

copy_function() {
  declare -F "$1" > /dev/null || return 1
  local func="$(declare -f "$1")"
  eval "${2}(${func#*\(}"
}

答案 5 :(得分:3)

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

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

可是:

  • 它可能不适用于递归函数,因为副本中用于递归的函数名称不会被替换。获得这样的替代权是一项非常复杂的任务。如果您想使用此类替换,则可以尝试此https://stackoverflow.com/a/18839557 whith 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

有人可能会说以下仍是一个错误:

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%的解决方案。如果需要改进,请评论;)

答案 6 :(得分:2)

这是一个基于@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

即可

请注意,您可以使用相关方法来修饰/修改/包装现有函数,如@ phs的回答:

# 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
}

答案 7 :(得分:1)

对于我们这些被迫与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}"))"
}

但这对我来说有点罗嗦

答案 8 :(得分:0)

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

有一种干净的方式来复制递归函数,这取决于Bash的晦涩之处。实际上,如此晦涩难懂,以至于找到申请书令我感到惊讶。就是这样。

但是,如下所述,此技巧不足以处理所有递归函数。这就是为什么我还提出另一种解决方案的原因,该解决方案可能更直接但也更昂贵。

使用别名的部分解决方案

说明

man bash中,“别名”部分(强调我的意思):

  

有关别名的定义和使用的规则有些   令人困惑。 Bash总是读取至少一整行输入,   执行任何以下命令之前,以及组成复合命令的所有行   该行上的命令或复合命令。 别名是   在读取命令时(而不是在执行命令时)展开。因此,   别名定义与另一个命令出现在同一行上   在读取下一行输入之前不生效。命令   该行上别名定义之后的内容不受   新别名。当函数是   被执行。 读取函数定义时,别名会展开,   ,因为函数定义是   本身就是命令。因此,函数中定义的别名为   在执行该功能之前不可用。为了安全起见   始终将别名定义放在单独的行上,而不使用别名   在复合命令中。

否则,当定义函数时,在 时存在的别名中所有出现的事件都会扩展。 (相反,调用该函数时,不会发生别名扩展。)可以利用它来替代函数体内的递归调用,而无需诉诸肮脏,不健全的sed调用。

正如其他答案所解释的那样,可以使用declare -fp $old_name获得要复制的函数的主体。然后,您将函数的新名称(而不是旧名称)放在此正文的顶部(使用Bash的变量替换机制),并将整个内容输入eval以定义新函数。

代码

下面提供的代码是按照@ingidotnet’s excellent answer的精神编写的,增加了对递归函数的支持。

  • 它仅使用shell内置函数,不使用外部程序(例如tailsed)。
  • 它不会执行不正确的文本替换(嗯,除了对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