2009-07-29 87 views
25

我正在開發另一個定義bash函數的軟件包的便利包裝。我想用我自己的同名命名函數替換它們的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 

我該怎麼做?

回答

37

這裏是消除臨時文件的方式:

$ 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

太棒了!謝謝你,bash嚮導;-) – 2009-09-02 18:14:05

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嚮導得到改善。特別是它可以很好地滿足臨時文件的需要。

更新:bash嚮導埃文布羅德上升到挑戰(見上面接受的答案)。我重新他的回答到一個通用的 「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 

很不錯的!

+7

Bash繼承!我從來沒有想過我會看到一天。 – shuckster 2009-08-16 22:48:45

+0

@carlmeyer謝謝!更新爲可重用函數 – Integralist 2016-08-17 19:59:21

+0

正如我在Evan的回答中提出的一個評論,另一種可能性也會處理遞歸函數:eval「$(declare -f theirfunc | sed's/\ btheirfunc \ b/orig_theirfunc/g) 「 – 2016-11-25 11:59:56

2

這是基於@Evan布羅德的做法功能:

# 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="[email protected]" 
    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="[email protected]" 
    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 
} 
5

copy_function可以通過使用shell參數e xpansion代替tail命令:

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

而且golfed的copy_functionrename_function功能:

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

rename_function() { 
    copy_function [email protected] || return 
    unset -f $1 
} 

從@Dmitri魯賓斯坦的解決方案開始:

  • 不需要調用declare兩次。錯誤檢查仍然有效。
  • 通過使用_特殊變量消除溫度變化(func)。
    • 注意:使用test -n ...是我能想到的唯一方法來保存_,並仍然能夠返回錯誤。
  • 變化return 1return(它返回當前狀態代碼)
  • 使用模式替換而不是前綴移除。

一旦copy_function被定義,它使rename_function瑣碎。(不要重命名copy_function ;-)

3

如果你只是想預先考慮到的東西的名字,說orig_,那麼我認爲最簡單的是

eval orig_"$(declare -f theirfun)" 
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%qbash內置)

當然你也可以拆分此成複製+改名這樣的:

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

我希望這是101%的解決方案。如果需要改進,請發表評論;)

0

對於我們這些被迫兼容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}"))" 
} 

但是,這是一個有點羅嗦對我來說