Reusing Code With Functions in Bash
20 min read·Jan 1, 2025
In programming, a function is a reusable code structure that allows you to regroup and encapsulate multiple instructions that are part of the same logic.
It allows you to re-run these instructions multiple times from different parts of a script, without manually repeating them.
In short, you can think of a function as a mini-script within the script.
Defining functions
To define a function in Bash, you can use the following syntax:
function_name () {
instructions
}
Where:
function_nameis the name of the function.instructionsare the set of instructions executed by the function.
Note: The instructions specified between the curly braces
{}are called the body of the function.
Example
Let's consider this script, that declares a function with a single instruction:
#!/bin/bash
hello () {
echo "Hello, World!"
}
When executed, it will:
- Define a function named
hello. - Declare in the body of the function a single instruction that outputs the string "Hello, World!".
Invoking functions
Just like any other available Bash commands, functions can be invoked (executed) by their name anywhere in the script, including within other functions:
function_name () {
instructions
}
function_name
Note: In Bash, functions must be declared before they are used.
Example
Let's consider this script, that executes a function:
#!/bin/bash
hello () {
echo "Hello, World!"
}
hello
When executed, it will:
- Define a function named
hello. - Declare in the body of the function a single instruction that outputs the string
"Hello, World!". - Invoke the
hellofunction.
Which will produce this output:
$ ./script.sh
Hello, World!
Passing arguments to functions
Similar to a script's positional arguments, function arguments are values passed to a function when invoked, such as strings, numbers, or arrays, allowing it to perform specific tasks or computations based on these values.
function_name value1 ... valueN
These arguments are then made accessible within the function's body using the same syntax as for positional arguments $N:
function_name () {
instruction $1 ... $N
}
Note: While positional arguments and function arguments share the same syntax
$N, positional arguments are not accessible within the body of a function unless they are explicitly passed as function arguments.
Example
Let's consider this script, that takes two numbers and output their sum:
#!/bin/bash
add () {
result=$(($1 + $2))
echo $result
}
add $1 $2
When executed, it will:
- Define a function named
add. - Store the sum of its parameters in a variable named
resultusing an arithmetic expansion. - Output the value of the
resultvariable. - Execute the
addfunction using as function arguments the first two positional arguments of the script.
Which will produce this output:
$ ./script.sh 1 2
3
$ ./script.sh 6 3
9
Setting a custom exit status
In Bash, the exit status of a function (or a command) is an integer value that represents the success or failure of the function's execution.
It is used to indicate whether the function completed its task as expected or encountered an error.
It allows the calling code to make decisions based on the outcome of the function and is accessible through the $? variable.
By convention, an exit status of 0 indicates that the function successfully completed without errors, and a non-zero exit status indicates that something went wrong.
Note: By default, a function's exit status is set to the value of the exit status of the last instruction executed by the function.
To set a custom exit status, you can use the return statement:
fname () {
instructions
return status
}
Whenever encountered, the return statement will immediately terminate the execution of the function.
In some cases, this behaviour can be leveraged to simplify the logic of a function by performing an early return instead of setting up an overly complex if-else structure.
Example
Let's consider this script, that uses the is_even function to determine whether an integer is even or odd:
#!/bin/bash
is_even () {
if [[ $(($1 % 2)) -eq 0 ]]; then
echo "$1 is even"
return 0
else
echo "$1 is odd"
return 1
fi
}
is_even 1
echo "Exit status is $?"
is_even 2
echo "Exit status is $?"
When executed, it will:
- Define a function named
is_even. - Check if the remainder of the division of the function parameter by 2 equals 0.
- If
true, output the value of the function parameter concatenated to the string"is even"and set the function's exit status to0. - If
false, output the value of the function parameter concatenated to the string"is odd"and set the function's exit status to1. - Invoke the
is_evenfunction by passing it1as parameter. - Output the exit status of the function.
- Invoke the
is_evenfunction by passing it2as parameter. - Output the exit status of the function.
Which will produce this output:
$ ./script.sh
1 is odd
Exit status is 1
2 is even
Exit status is 0
Note:
Since the
returnstatement immediately terminates the execution of the function, we can simplify theis_evenfunction by removing the unnecessaryelsestatement, which will produce the same result:is_even () { if [[ $(($1 % 2)) -eq 0 ]]; then echo "$1 is even" return 0 fi echo "$1 is odd" return 1 }
Capturing the output of a function
Unlike in other programming languages, Bash functions cannot directly return data, such as strings, numbers, or arrays.
However, it is possible to capture the data written by function or command to the standard output into a variable using the command substitution expansion $().
output=$(command)
Example
Let's consider this script, that outputs the sum of two integers:
#!/bin/bash
add () {
echo $(($1 + $2))
}
sum=$(add 1 2)
echo $sum
When executed, it will:
- Define a function named
addthat outputs the sum of its first two function parameters. - Store the output of the
addfunction's execution into a variable namedsumusing the command substitution expansion. - Output the value of the
sumvariable.
Which will produce this output:
$ ./script.sh
3
Variable scopes
In programming, the scope of a variable refers to the area of the code where a variable can be accessed and used.
In Bash, there are two main types of variable scope: global and local.
Global variables
Global variables are variables that are accessible and modifiable from anywhere in the script, including within functions.
They are usually declared and assigned values outside of any function or structure, at the top of the script.
They are generally used to store values that need to be shared across functions or for data that needs to maintain its state throughout the entire script, referred to as constants.
Example
Let's consider this script, that increments a global variable in different ways:
#!/bin/bash
count=0
increment () {
count=$((count + 1))
}
increment
count=$((count + 1))
echo "\$count = $count"
When executed, it will:
- Declare a global variable named
countand initialize it to0. - Declare a function named
incrementthat when invoked increases the value of the variablecountby1. - Invoke the
incrementfunction. - Increase the value of the
countvariable by1. - Output the value of the
countvariable.
Which will produce this output:
$ ./script.sh
$count = 2
Local variables
Local variables are variables that are only accessible and modifiable from within the function they are defined in.
They are used to store temporary values that should be isolated from the rest of the script, and only exist for the function's execution duration.
They are declared using the local keyword.
function () {
local variable=value
}
Note: Local variables do not affect or interact with variables of the same name in the global scope or other functions. If a local variable shares the same name as a global variable, the local variable takes precedence within the function, which is known as variable shadowing.
Example
Let's consider this script, that illustrates variable shadowing using a global and a local variable with the same name:
#!/bin/bash
result=42
multiply () {
local result=$(($1 * $2))
echo "(local) \$result = $result"
}
multiply 3 2
echo "(global) \$result = $result"
When executed, it will:
- Declare a global variable named
resultand initialize it to42. - Define a function named
multiplythat multiplies its first two function parameters, stores their product in a local variable namedresult, and outputs the value of the variableresult. - Invoke the
multiplyfunction with the values3and2as parameters. - Output the value of the global
resultvariable.
Which will produce this output:
$ ./script.sh
(local) $result = 6
(global) $result = 42
Overriding existing commands
In Bash, you can override existing commands by creating a custom function with the same name but a different implementation.
When executed, this function will take precedence over the original command and be executed instead of it.
This mechanism is often referred to as command hijacking or function overloading.
shell_command () {
# new implementation
}
Note:
To invoke a shell command within a shell function of the same name, you have to invoke it using the
commandcommand, as otherwise, your script will end up in an infinite call loop.shell_command () { command shell_command ... }
Example
Let's consider this script, that overrides the shell's ls command:
#!/bin/bash
ls () {
echo "Executing the ls function..."
command ls -l $@
}
ls ~
When executed, it will:
- Define a function named
ls. - Invoke the shell's
lscommand with the-lflag and all the function parameters supplied to the function. - Invoke the
lsfunction with the tilde expansion~.
Which will produce this output:
$ ./script.sh
Executing the ls function...
total 400
drwx------@ 7 razvan staff 224 Oct 26 21:21 Desktop
drwx------@ 14 razvan staff 448 May 27 10:38 Documents
drwx------@ 29 razvan staff 928 Oct 26 15:15 Downloads
drwx------@ 105 razvan staff 3360 May 29 07:02 Library
drwx------ 20 razvan staff 640 Aug 30 16:20 Projects
drwxr-xr-x+ 4 razvan staff 128 Jun 14 2018 Public
Summary
Here's a summary of what you've learned in this lesson:
- A function is a reusable block of code that performs a specific task or set of tasks.
- A function is defined using the syntax
function_name () { instructions }. - A function is invoked by its name like any other command.
- A function can take zero or more arguments like any other command.
- The exit status of a command is set using the
returnstatement. - The output of a command can be captured using a command expansion
$(command). - Global variables are accessible and modifiable from anywhere in the script.
- Local variables are declared using the
readonlykeyword and are only accessible and modifiable from within the function they are defined in - A function can override another function or command by being declared under the same name.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery