如何重命名 bash 函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1203583/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 21:05:45  来源:igfitidea点击:

How do I rename a bash function?

bashfunction

提问by Carl Meyer

I am developing some convenience wrappers around another software package that defines a bash function. I would like to replace their bash function with an identically-named function of my own, while still being able to run their function from within mine. In other words, I need to either rename their function, or create some kind of persistent alias to it that won't be modified when I create my function of the same name.

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

To give a brief example of a naive attempt that I didn't expect to work (and indeed it does not):

举一个我没想到会起作用的幼稚尝试的简短示例(实际上它不起作用):

$ 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
...

Obviously I don't want infinite recursion, I want:

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

do my thing
do their thing

How can I do this?

我怎样才能做到这一点?

回答by Evan Broder

Here's a way to eliminate the temp file:

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

$ 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

回答by ingydotnet

Further golfed the copy_functionand rename_functionfunctions to:

进一步打高尔夫球copy_functionrename_function功能:

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

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

Starting from @Dmitri Rubinstein's solution:

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

  • No need to call declaretwice. Error checking still works.
  • Eliminate temp var (func) by using the _special variable.
    • Note: using test -n ...was the only way I could come up with to preserve _and still be able to return on error.
  • Change return 1to return(which returns the current status code)
  • Use a pattern substitution rather than prefix removal.
  • 不需要打电话declare两次。错误检查仍然有效。
  • func使用_特殊变量消除 temp var ( ) 。
    • 注意:使用test -n ...是我能想出的唯一方法来保留_并且仍然能够在出错时返回。
  • 更改return 1return(返回当前状态码)
  • 使用模式替换而不是前缀删除。

Once copy_functionis defined, it makes rename_functiontrivial. (Just don't rename copy_function;-)

一旦copy_function被定义,它就变得rename_function微不足道了。(只是不要重命名copy_function;-)

回答by Carl Meyer

Aha. Found a solution, though it's not real pretty:

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

$ 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

I'm sure this could be improved by a real bash wizard. In particular it'd be nice to ditch the need for a tempfile.

我相信这可以通过真正的 bash 向导来改进。特别是放弃对临时文件的需求会很好。

Update: bash wizard Evan Broder rose to the challenge (see accepted answer above). I reformulated his answer into a generic "copy_function" function:

更新:bash 向导 Evan Broder 接受了挑战(请参阅上面已接受的答案)。我将他的答案重新表述为通用的“copy_function”函数:

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

Can be used like so:

可以这样使用:

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

Very nice!

非常好!

回答by parched

If you just want to prepend something to the name, say orig_, then I think the simplest is

如果你只是想在名字前面加上一些东西,比如orig_,那么我认为最简单的是

eval orig_"$(declare -f theirfun)"

回答by Dmitri Rubinstein

The copy_function can be improved by using shell parameter expansion instead of tail command:

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

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

回答by Tino

To sum up all the other solutions and partially correct them, here is the solution which:

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

  • does not use declaretwice
  • does not need external programs (like tail)
  • does no unexpected replacements
  • is relatively short
  • protects you against usual programming bugs thanks to correct quoting
  • 不使用declare两次
  • 不需要外部程序(如tail
  • 没有意外的替代品
  • 比较短
  • 由于正确的引用,可以保护您免受常见的编程错误

But:

但:

  • It probably does not work on recursive functions, as the function name used for recursion within the copy is not replaced. Getting such a replacement right is a much too complex task. If you want to use such replacements, you can try this answer https://stackoverflow.com/a/18839557whith eval "${_//$1/$2}"instead of eval "${_/$1/$2}"(Note the double //). However replacing the name fails on too simple function names (like a) and it fails for calculated recursion (like command_help() { case "$1" in ''|-help) echo "help [command]"; return;; esac; "command_$1" -help "${@:2}"; })
  • 它可能不适用于递归函数,因为副本中用于递归的函数名称不会被替换。获得这样的替代品是一项非常复杂的任务。如果你想使用这些替代品,你可以试试这个答案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}"; }

Everything combined:

一切结合起来:

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

now the tests:

现在测试:

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

outputs as required:

根据需要输出:

two
one

Now try some more complicated cases, which all give the expected results or fails:

现在尝试一些更复杂的情况,它们都会给出预期的结果或失败:

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

One can argue that following is still a bug:

有人可以争辩说以下仍然是一个错误:

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

as it should fail as " b "is not a correct function name. If you really want this, you need following variant:

因为它应该失败,因为" b "它不是正确的函数名称。如果你真的想要这个,你需要以下变体:

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

Now this catches this artificial case, too. (Please note that printfwith %qis a bashbuiltin.)

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

Of course you can split this up into copy+rename like this:

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

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

I hope this is the 101% solution. If it needs improvement, please comment ;)

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

回答by Andy

For those of us forced to be compatible with bash 3.2 (you know who we are talking about), declare -fdoesn't work. I found typecan work

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

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

In function form, it would look like

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

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

And if you really want to not rely on sed...

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

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

But that's a bit wordy for me

但这对我来说有点罗嗦

回答by Gareth Stockwell

Here is a function based on @Evan Broder's approach:

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

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

Once this is defined, you can simply do rename_function func orig_func

一旦定义了这个,你就可以简单地做 rename_function func orig_func

Note that you can use a related approach to decorate/modify/wrap existing functions, as in @phs's answer:

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

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

Once these are defined, let's say you have an existing function as follows:

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

function foo()
{
    echo stuff
}

Then you can do:

然后你可以这样做:

prepend_to_function foo echo before
append_to_function foo echo after

Using declare -f foo, we can see the effect:

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

foo ()
{
    echo before;
    echo stuff;
    echo after
}

回答by Ma?lan

I know it's an old question, but no one has addressed the issue with recursion yet.

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

There is a clean way of copying recursive functions, relying on an obscure corner of Bash. So obscure, in fact, that finding an application for it came as a surprise to me. So here is it.

有一种复制递归函数的干净方法,依赖于 Bash 的一个不起眼的角落。事实上,如此晦涩难懂,以至于我为它找到了一个应用程序让我感到惊讶。所以就是这样。

However, as explained below, this trick is not enough to handle all recursive functions. That is why I also present another solution, which may be more straightforward but also much more costly.

但是,如下所述,这个技巧不足以处理所有递归函数。这就是为什么我还提出了另一种解决方案,它可能更直接,但成本也更高。

A partial solution using aliases

使用别名的部分解决方案

Explanation

解释

From man bash, section “ALIASES” (emphasis mine):

来自man bash,“别名”部分(强调我的):

The rules concerning the definition and use of aliases are somewhat confusing. Bash always reads at least one complete line of input, and all lines that make up a compound command, before executing any of the commands on that line or the compound command. Aliases are expanded when a command is read, not when it is executed.Therefore, an alias definition appearing on the same line as another command does not take effect until the next line of input is read. The commands following the alias definition on that line are not affected by the new alias. This behavior is also an issue when functions are executed. Aliases are expanded when a function definition is read, not when the function is executed, because a function definition is itself a command. As a consequence, aliases defined in a function are not available until after that function is executed. To be safe, always put alias definitions on a separate line, and do not use alias in compound commands.

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

Otherwise said, when a function is defined, all occurrences in its body of aliases which exist at thattime are expanded. (And, conversely, no alias expansion takes place when the function is called.) This can be exploited to substitute recursive calls inside a function body, without resorting to dirty, unsound sedinvocations.

否则说,定义一个函数时,在其地存在的别名身上所有出现的那个时间被扩展。(相反,调用函数时不会发生别名扩展。)这可以用来替代函数体内的递归调用,而无需求助于肮脏、不健全的sed调用。

As already explained by other answers, the body of the function to copy can be obtained with declare -fp $old_name. You then put the new name of the function on top of this body instead of the old name (using Bash's variable substitution mechanism), and feed the whole thing to evalto define the new function.

正如其他答案已经解释的那样,要复制的函数的主体可以通过declare -fp $old_name. 然后将函数的新名称而不是旧名称(使用 Bash 的变量替换机制)放在该主体的顶部,并将整个内容提供eval给定义新函数。

Code

代码

The code presented below is written in the spirit of @ingidotnet's excellent answer, adding to it support for recursive functions.

下面提供的代码是本着@ingidotnet 的优秀回答的精神编写的,增加了对递归函数的支持。

  • It only uses shell builtins, no external programs (such as tailor sed).
  • It does not perform unsound, textual substitutions (well, except for a very small assumption about the output format of declare -fp).
  • It is correctly quoted.
  • It supports copying somerecursive functions.
  • 它只使用 shell 内置程序,没有外部程序(例如tailsed)。
  • 它不会执行不合理的文本替换(好吧,除了关于 的输出格式的一个非常小的假设declare -fp)。
  • 它被正确引用。
  • 它支持复制一些递归函数。

However there is a pitfall: the alias trick apparently does not catch all possible recursive calls. It misses at least the calls of the form $(old_name ...).

但是有一个陷阱:别名技巧显然没有捕获所有可能的递归调用。它至少错过了表单的调用$(old_name ...)

function copy_function() {
    declare old=""
    declare new=""
    # 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 ""
}

Example 1

示例 1

The following code:

以下代码:

# a recursive function which prints a range of numbers
function enum() {
    declare -i i=""
    declare -i j=""
    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

will work as expected (tested with bash 5.0.7):

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

bash: declare: enum: not found
range () 
{ 
    declare -i i="";
    declare -i j="";
    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

Example 2

示例 2

However, the following recursive function won't be properly renamed.

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

# the Fibonacci function
function fib() {
    declare -i n=""
    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

The output is:

输出是:

bash: declare: fib: not found
FIB () 
{ 
    declare -i n="";
    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

A complete but heavier solution using function re-definitions

使用函数重新定义的完整但更重的解决方案

Here is an alternative approach. Simply define the new function as a wrapper function which re-defines the original function locally and calls it.

这是一种替代方法。只需将新函数定义为包装函数,该函数在本地重新定义原始函数并调用它。

As compared to the alias trick, this tackles all recursive calls, but is much more costly, as the original function is be re-defined and restored at every call of the new function.

与别名技巧相比,这解决了所有递归调用,但成本更高,因为在每次调用新函数时都会重新定义和恢复原始函数。

Code

代码

Here is the code corresponding to that idea. To the best of my knowledge, it has no remaining flaw.

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

function copy_function() {
    declare old=""
    declare new=""
    # 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"
    )"
}

Example 2

示例 2

This time, example 2 from above works as expected:

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

bash: declare: fib: not found
FIB () 
{ 
    declare former_def="$(declare -fp fib 2>/dev/null)";
    function fib () 
    { 
        declare -i n="";
        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