Bash: pass a function as parameter

BashFunctionCallbackParameter Passing

Bash Problem Overview


I need to pass a function as a parameter in Bash. For example, the following code:

function x() {
  echo "Hello world"
}

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

around x

Should output:

before
Hello world
after

I know eval is not correct in that context but that's just an example :)

Any idea?

Bash Solutions


Solution 1 - Bash

If you don't need anything fancy like delaying the evaluation of the function name or its arguments, you don't need eval:

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

around x

does what you want. You can even pass the function and its arguments this way:

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

around x 1st 2nd

prints

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

Solution 2 - Bash

I don't think anyone quite answered the question. He didn't ask if he could echo strings in order. Rather the author of the question wants to know if he can simulate function pointer behavior.

There are a couple of answers that are much like what I'd do, and I want to expand it with another example.

From the author:

function x() {
  echo "Hello world"
}

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

around x


To expand this, we will have function x echo "Hello world:$1" to show when the function execution really occurs. We will pass a string that is the name of the function "x":

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

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

around x

To describe this, the string "x" is passed to the function around() which echos "before", calls the function x (via the variable $1, the first parameter passed to around) passing the argument "HERE", finally echos after.

As another aside, this is the methodology to use variables as function names. The variables actually hold the string that is the name of the function and ($variable arg1 arg2 ...) calls the function passing the arguments. See below:

function x(){
    echo $3 $1 $2      <== just rearrange the order of passed params
}

Z="x"        # or just Z=x

($Z  10 20 30)

gives: 30 10 20, where we executed the function named "x" stored in variable Z and passed parameters 10 20 and 30.

Above where we reference functions by assigning variable names to the functions so we can use the variable in place of actually knowing the function name (which is similar to what you might do in a very classic function pointer situation in c for generalizing program flow but pre-selecting the function calls you will be making based on command line arguments).

In bash these are not function pointers, but variables that refer to names of functions that you later use.

Solution 3 - Bash

there's no need to use eval

function x() {
  echo "Hello world"
}

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

around x

Solution 4 - Bash

You can't pass anything to a function other than strings. Process substitutions can sort of fake it. Bash tends to hold open the FIFO until a command its expanded to completes.

Here's a quick silly one

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)

Functions can be exported, but this isn't as interesting as it first appears. I find it's mainly useful for making debugging functions accessible to scripts or other programs that run scripts.

(
    id() {
        "$@"
    }

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

id still only gets a string that happens to be the name of a function (automatically imported from a serialization in the environment) and its args.

Pumbaa80's comment to another answer is also good (eval $(declare -F "$1")), but its mainly useful for arrays, not functions, since they're always global. If you were to run this within a function all it would do is redefine it, so there's no effect. It can't be used to create closures or partial functions or "function instances" dependent on whatever happens to be bound in the current scope. At best this can be used to store a function definition in a string which gets redefined elsewhere - but those functions also can only be hardcoded unless of course eval is used

Basically Bash can't be used like this.

Solution 5 - Bash

A better approach is to use local variables in your functions. The problem then becomes how do you get the result to the caller. One mechanism is to use command substitution:

function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

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

Here the result is output to the stdout and the caller uses command substitution to capture the value in a variable. The variable can then be used as needed.

Solution 6 - Bash

You should have something along the lines of:

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

You can then call around x

Solution 7 - Bash

eval is likely the only way to accomplish it. The only real downside is the security aspect of it, as you need to make sure that nothing malicious gets passed in and only functions you want to get called will be called (along with checking that it doesn't have nasty characters like ';' in it as well).

So if you're the one calling the code, then eval is likely the only way to do it. Note that there are other forms of eval that would likely work too involving subcommands ($() and ``), but they're not safer and are more expensive.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questioncd1View Question on Stackoverflow
Solution 1 - BashIdelicView Answer on Stackoverflow
Solution 2 - BashuDudeView Answer on Stackoverflow
Solution 3 - BashkurumiView Answer on Stackoverflow
Solution 4 - BashormaajView Answer on Stackoverflow
Solution 5 - BashAnand ThangappanView Answer on Stackoverflow
Solution 6 - BashTim OView Answer on Stackoverflow
Solution 7 - BashWes HardakerView Answer on Stackoverflow