Bash:将函数作为参数传递

113

我需要在Bash中将一个函数作为参数传递。例如,下面的代码:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  eval $1
  echo "after"
}

around x

应该输出:

before
Hello world
after
我知道在那个上下文中使用eval并不正确,但这只是一个例子 :) 你有什么想法吗?
7个回答

162

如果您不需要任何高级操作,比如延迟评估函数名称或其参数,就不需要使用eval

function x()      { echo "Hello world";          }
function around() { echo before; $1; echo after; }

around x

它可以达到你想要的效果。你甚至可以通过这种方式传递函数及其参数:

function x()      { echo "x(): Passed $1 and $2";  }
function around() { echo before; "$@"; echo after; }

around x 1st 2nd

打印

before
x(): Passed 1st and 2nd
after

3
如果我有另一个y()函数,我可以做around x 1st 2nd y 1st 2nd吗?它如何知道x和y是around的参数,而1st和2nd是x和y的参数? - techguy2000
你可以省略函数词。 - jasonleonhard
然而,它们将不会被命名空间化。即,如果您在方法内部拥有方法,并且保留了 function 关键字,则在运行顶层方法之前无法访问这些内部方法。 - jasonleonhard

48

我认为没有人完全回答了这个问题。他并没有问是否可以按顺序回显字符串。相反,问题的作者想知道是否可以模拟函数指针行为。

有几个答案很像我会做的事情,我想用另一个例子来扩展它。

来自作者:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  ($1)                   <------ Only change
  echo "after"
}

around x

为了扩展这个功能,我们将有函数x echo "Hello world:$1"来显示函数执行的时间。我们将传递一个字符串,即函数“x”的名称:

function x() {
  echo "Hello world:$1"
}

function around() {
  echo "before"
  ($1 HERE)                   <------ Only change
  echo "after"
}

around x
为了描述这个,将字符串“x”传递给函数around(),该函数回显“before”,通过变量$1(传递给around的第一个参数)调用函数x,并传递参数“HERE”,最后回显after。
另外,使用变量作为函数名称的方法是在变量中保存函数名称的字符串,而($variable arg1 arg2 ...)则调用并传递参数给该函数。请参见下面:
function x(){
    echo $3 $1 $2      <== just rearrange the order of passed params
}

Z="x"        # or just Z=x

($Z  10 20 30)

执行了存储在变量Z中的名为“x”的函数,并传递参数10,20和30。结果为30 10 20。

上述是通过将变量名分配给函数来引用函数,这样我们可以使用变量代替实际知道函数名称(类似于在c中非常经典的函数指针情况下泛化程序流程但预先选择要基于命令行参数进行的函数调用)。

在bash中,这些不是函数指针,而是引用稍后使用的函数名称的变量。


1
这个答案太棒了。我制作了所有示例的bash脚本并运行它们。我也非常喜欢你制作的“仅更改”标记,这帮了我很多忙。你倒数第二段有一个拼写错误:“Aabove”。 - KANJICODER
错别字已经修正。感谢 @J MADISON。 - uDude
11
为什么要用 () 把它包起来?这不会启动一个子 shell 吗? - horseyguy

19

不必使用eval

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  var=$($1)
  echo "after $var"
}

around x

5

除了字符串,您无法向函数传递任何内容。进程替代可以模拟它。Bash倾向于保持FIFO打开状态,直到扩展到的命令完成。

这是一个简单而愚蠢的例子。

foldl() {
    echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)

# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)

函数可以被导出,但这并不像一开始看起来那么有趣。我发现它主要用于使调试函数对脚本或运行脚本的其他程序可访问。

(
    id() {
        "$@"
    }

    export -f id
    exec bash -c 'echowrap() { echo "$1"; }; id echowrap hi'
)

id仍然只能获取一个字符串,该字符串恰好是函数的名称(从环境中自动导入的序列化)及其参数。

Pumbaa80对另一个答案的评论也很好(eval $(declare -F "$1")),但它主要适用于数组,而不是函数,因为它们总是全局的。如果您在函数内部运行此代码,则仅会重新定义它,因此没有任何效果。它不能用于创建闭包或部分函数或依赖于当前作用域中绑定的任何内容的“函数实例”。最多可以将函数定义存储在字符串中,并在其他位置重新定义-但这些函数也只能硬编码,除非使用eval

基本上,Bash无法像这样使用。


2
更好的方法是在您的函数中使用本地变量。问题则变成如何将结果传递给调用者。一种机制是使用命令替换:
function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

result=$(myfunc)   # or result=`myfunc`
echo $result

这里的结果被输出到标准输出,调用者使用命令替换来捕获变量中的值。然后可以根据需要使用该变量。


1

你应该有类似以下的内容:

function around()
{
  echo 'before';
  echo `$1`;
  echo 'after';
}

您可以调用around x

-1

eval 可能是实现它的唯一方法。唯一真正的缺点是安全方面,因为您需要确保没有恶意内容被传递,并且只会调用您想要调用的函数(同时检查它是否有像 ';' 这样的恶意字符)。

因此,如果您是调用代码的人,则 eval 可能是唯一的方法。请注意,还有其他形式的 eval 也可能有效,涉及子命令 ($() 和 ``),但它们不够安全并且更昂贵。


eval是唯一的方法。 - Wes
1
你可以使用 if declare -F "$1" >/dev/null; then eval $1; fi 轻松检查 eval $1 是否会调用一个函数。 - user123444555621
2
甚至更好的是:eval $(declare -F "$1") - user123444555621

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接