Using Functions in Bash

When your bash scripts get bigger and bigger, things can get very messy!

You may find yourself rewriting the same pieces of code again and again in different parts of your bash scripts.

Luckily, you can avoid rewriting code by using functions in bash which will make your scripts more organized and readable.

In this tutorial, you will learn to create functions, return function values, and pass function arguments in bash shell scripts.

Furthermore, you will learn how variables scope work and how to define recursive functions.

Creating functions in bash

There are two different syntaxes for declaring bash functions. The following syntax is the most common used way of creating bash functions:

function_name () {
commands
}

The second less commonly used of creating bash functions starts with the reserved work function followed by the function name as follows:

function function_name {
commands
}

Now there are a couple of things you should be well aware of when working with functions:

  • A function will never run/execute unless you invoke/call the function.
  • The function definition must precede any calls to the function.

Anytime you want a function to run, you just need to call it! A function call is done by simply referencing the function name.

Take a look at the following fun.sh bash script:

#!/bin/bash

hello () {
echo "Hello World"
}

hello
hello
hello

I defined a function named hello that simply echo’s the line “Hello World” to the terminal. Notice that I did three hello function calls and so if you run the script, you will see the “Hello World” line printed three times on the screen:

kabary@handbook:~$ ./fun.sh
Hello World
Hello World
Hello World

Returning function values in bash

In many programming languages, functions do return a value when called; however, this is not the case with bash as bash functions do not return values.

When a bash function finishes executing, it returns the exit status of the last command executed captured in the $? variable. Zero indicates successful execution or a non-zero positive integer (1-255) to indicate failure.

You can use a return statement to alter the function’s exit status. For example, take a look at the following error.sh script:

#! /bin/bash

error () {
blabla
return 0
}

error
echo "The return status of the error function is: $?"

If you run the error.sh bash script, you might be surprised of the output:

kabary@handbook:~$ ./error.sh
./error.sh: line 4: blabla: command not found
The return status of the error function is: 0

Without the return 0 statement, the error function would have never returned a non-zero exit status as blabla is results in a command not found error.

So as you can see, even though bash functions do not return values, I made a workaround by altering function exit statuses.

You should also be aware that a return statement immediately terminates a function.

Passing arguments to bash function

You can pass arguments to a function just like you can pass arguments to a bash script. You just include the arguments when you do the function call.

To demonstrate, let’s take a look at the following iseven.sh bash script:

#!/bin/bash

iseven () {
if [ $(($1 % 2)) -eq 0 ]; then
echo "$1 is even."
else
echo "$1 is odd."
fi
}

iseven 3
iseven 4
iseven 20
iseven 111

The iseven() function tests whether a number is even or odd. I did four function calls to iseven(). For each function call, I supplied one number which is the first augment to the iseven() function and is referenced by the $1 variable in the function definition.

Let’s run the iseven.sh bash script to make sure it works:

kabary@handbook:~$ ./iseven.sh
3 is odd.
4 is even.
20 is even.
111 is odd.

You should also be well aware that bash function arguments and bash script arguments are two different things. To contrast the difference, take a look at the following funarg.sh bash script:

#!/bin/bash

fun () {
echo "$1 is the first argument to fun()"
echo "$2 is the second argument to fun()"
}

echo "$1 is the first argument to the script."
echo "$2 is the second argument to the script."

fun Yes 7

Run the script with a couple of arguments and observe the result:

kabary@handbook:~$ ./funarg.sh Cool Stuff
Cool is the first argument to the script.
Stuff is the second argument to the script.
Yes is the first argument to fun()7 is the second argument to fun()

As you can see, even though you used the same variables $1 and $2 to refer to both the script arguments and the function arguments, they produce different results when called from within a function.

Local and global variables in bash functions

Bash variables can either have a global or local scope. You can access a global variable anywhere in a bash script regardless of the scope. On the contrary, a local variable can only be accessed from within their function definition.

To demonstrate, take a look at the following scope.sh bash script:

#!/bin/bash

v1='A'
v2='B'

myfun() {
local v1='C'
v2='D'
echo "Inside myfun(): v1: $v1, v2: $v2"
}

echo "Before calling myfun(): v1: $v1, v2: $v2"
myfun
echo "After calling myfun(): v1: $v1, v2: $v2"

I first defined two global variables v1 and v2. Then inside myfun() definition, I used the local keyword to define a local variable v1 and modified the global variable v2. Note that you can use the same variable name for local variables in different functions.

Now let’s run the script:

kabary@handbook:~$ ./scope.sh
Before calling myfun(): v1: A, v2: B
Inside myfun(): v1: C, v2: D
After calling myfun(): v1: A, v2: D

From the script output, you can conclude the following:

  • A Local variable that have the same name as a global variable will take precedence over global variables inside a function body.
  • You can change a global variable from within a function.

Recursive functions

A recursive function is a function that calls itself! Recursive functions come in handy when you attempt to solve a programming problem that can be broken down to smaller subproblems.

The factorial function is a classic example of a recursive function. Take a look at the following factorial.sh bash script:

#!/bin/bash

factorial () {
if [ $1 -le 1 ]; then
echo 1
else
last=$(factorial $(( $1 -1)))
echo $(( $1 * last ))
fi
}

echo -n "4! is: "
factorial 4
echo -n "5! is: "
factorial 5
echo -n "6! is: "
factorial 6

Any recursive function must begin with a base case which is necessarily to end the chain of recursive function calls. In the factorial() function, the base case is defined as follows:

if [ $1 -le 1 ]; then
echo 1

Now derive the recursive case for the factorial function. To calculate the factorial of a number n where n is a positive number greater than one, you can multiply n by the factorial of n-1:

factorial(n) = n * factorial(n-1)

Let's use the above equation to write this recursive case:

last=$(factorial $(( $1 -1)))
echo $(( $1 * last ))

Now run the script and make sure to get the correct results:

kabary@handbook:~$ ./factorial.sh
4! is: 24
5! is: 120
6! is: 720

As an additional exercise, try to write a recursive function to calculate the nth Fibonacci number. First, try to come up with the base case and then the recursive case; you got this!

You need more exercise? Download the PDF below and practice functions in bash scripts. You also have the solutions in the same PDF.

Awesome! The next and final chapter of this series will apply everything you have learned so far to write effective bash scripts that automate boring administrative tasks.