如何重命名 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
How do I rename a bash function?
提问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_function和rename_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.
- Note: using
- Change
return 1toreturn(which returns the current status code) - Use a pattern substitution rather than prefix removal.
- 不需要打电话
declare两次。错误检查仍然有效。 func使用_特殊变量消除 temp var ( ) 。- 注意:使用
test -n ...是我能想出的唯一方法来保留_并且仍然能够在出错时返回。
- 注意:使用
- 更改
return 1为return(返回当前状态码) - 使用模式替换而不是前缀删除。
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 ofeval "${_/$1/$2}"(Note the double//). However replacing the name fails on too simple function names (likea) and it fails for calculated recursion (likecommand_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
tailorsed). - 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 内置程序,没有外部程序(例如
tail或sed)。 - 它不会执行不合理的文本替换(好吧,除了关于 的输出格式的一个非常小的假设
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

