我正在围绕另一个定义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
我该怎么做?
答案 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_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
特殊变量消除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
)可是:
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的精神编写的,增加了对递归函数的支持。
tail
或sed
)。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"
}
以下代码:
# 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
但是,以下递归函数无法正确重命名。
# 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可以正常工作:
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