1. Overview
Bash lacks a dedicated syntax for function pointers, which allow for direct referencing and invocation of functions through memory addresses. However, it offers workarounds to achieve similar effects.
In this tutorial, we’ll explore how to declare a function in Bash and pass it as an argument to another function.
We’ll use the terms “argument” and “parameter” interchangeably, although there is a subtle difference that we won’t go into.
2. Declaring Functions in Bash
Bash functions are named blocks of code that we can reuse multiple times within a script. So, they help us to organize our code and break it into smaller, more manageable chunks.
We can use the following syntax to declare a function in Bash:
function_name() {
# Function code goes here
}
For example, we can create a function that prints a greeting message:
greet() {
echo "Hello Baeldung!"
}
In a script, we can call this function as follows:
greet
Such a function call produces the greeting Hello Baeldung! in the terminal.
Now, let’s say we want to pass a parameter to this function. Instead of printing Hello, we want to print the value of the given parameter, for example, Good morning:
greet() {
echo "$1 Baeldung!"
}
greet "Good morning"
The previous code prints, Good morning Baeldung!. Let’s see how to pass a function as a parameter next.
3. Passing a Function as a Parameter
Let’s say we want to pass to our greet() function this other function that prints a different greeting based on the time of day:
create_greet() {
current_hour=$(date +%H)
# We need to use 10#$current_hour to prevent numbers with a leading zero from being interpreted as octal
if (( 10#$current_hour >= 5 && 10#$current_hour < 12 )); then
echo "Good morning"
elif (( 10#$current_hour >= 12 && 10#$current_hour < 18 )); then
echo "Good afternoon"
elif (( 10#$current_hour >= 18 && 10#$current_hour < 22 )); then
echo "Good evening"
else
echo "Good night"
fi
}
There are three different ways to pass create_greet() as a parameter to greet():
- command substitution
- direct variable reference
- indirect variable reference
The first method is the easiest to understand and doesn’t require us to rewrite greet(). In this case, we’ll just pass the output of create_greet() to greet().
In contrast, the second and third methods simulate the behavior of function pointers because they don’t pass the output of create_greet() to greet() but the function itself. As a result, we’ll have to rewrite the implementation of greet() accordingly.
3.1. Command Substitution
It’s very easy. We just have to be careful not to forget the double quotes:
greet "$(create_greet)"
It prints the expected greeting based on the time, e.g., Good night Baeldung! at 1:40 am.
Since we’re just passing the value returned by the create_greet() function, which is treated like a command via the $(…) command substitution syntax, it’s not really a function passed as a parameter. However, it’s conceptually similar to what we can do in object-oriented programming languages such as Java and others, which allow us to pass to a method the value returned by another method. In fact, in pure object-oriented languages, the concept of passing a function as a parameter doesn’t even exist.
3.2. Direct Variable Reference
Let’s pass the function name as a parameter:
greet create_greet
The current implementation of greet() will print create_greet Baeldung! because it treats the function name passed as a parameter as a text string. So, let’s change the implementation of greet():
greet() {
local mygreet=$($1) # $1 is the passed function
echo "$mygreet Baeldung!"
}
$1 is a special positional variable and indicates the first (and, in this case, only) parameter passed to the greet() function, i.e., the text string create_greet. This string is then treated as if it were the name of a command because the $(…) syntax is a command substitution. We saw earlier that command substitution also works with functions, which Bash treats as if they were commands.
After this change, greet() executes the function we passed to it and thus prints the correct output.
3.3. Indirect Variable Reference
In this case, instead of passing the create_greet() function directly, we assign its name to the myfunc variable and then pass myfunc as a parameter:
myfunc=create_greet # indirect reference to the function
greet myfunc
To allow greet() to work with this indirect variable reference, we need to modify its implementation:
greet() {
local function=$1 # indirect reference to a function
local actual_func=${!function} # indirect expansion
local mygreet=$($actual_func) # command substitution
echo "$mygreet Baeldung!"
}
This method of indirect reference can be confusing. When the first character of a parameter is an exclamation mark, as in the case of !function, Bash understands that it’s an indirect reference to the variable whose name is the value of that parameter. So, in this case, function is a local variable whose value is the value of the special positional variable $1, which is myfunc. The value of the variable myfunc is create_greet, so !function refers to create_greet. We might think of simplifying the code with actual_func=${!$1}, but that wouldn’t work and would cause an error.
In general, it’s wise to avoid writing code that’s difficult to understand, so indirect variable references require special care.
4. Conclusion
In this article, we’ve seen how to declare a function in Bash and pass it as an argument to another function. As an example, we passed the function create_greet() to greet().
Overall, passing functions as arguments allows for greater flexibility, modularity, and customization. It allows us to write more reusable code, implement higher-order functions (HOF), and dynamically adjust the behavior of our functions based on different conditions.