Home Bash scripting Bash Scripting – Functions Explained With Examples

Bash Scripting – Functions Explained With Examples

By Karthick
Published: Last Updated on 2,823 Views

In Bash shell scripting, functions are ways to group the set of instructions together to get a specific outcome. You can think of functions as a mini script. Functions are also called procedures and methods in some programming languages. Functions are a great way to achieve modularity and reusability.

In this article, I will explain how to use functions in bash scripts in Linux with examples. You will be pretty comfortable in using bash functions by the end of this article.

How to define Functions in Bash

There are two important things you have to understand when working with functions.

  • Defining the function,
  • Calling the function.

Similar to how you create bash scripts and run them, you should define the function and call it for the function to run.

There are two syntactical ways to define a function in bash. The first way is to use the bash built-in keyword "function" followed by the name of the function. The block of code will be written inside curly braces {}.

function [name] {
Block of code
}

The second way is to create a function without the keyword "function". Start with the function name followed by brackets.

[name](){
Block of Code
}

Which one to choose? Well, it is always a personal choice. Choosing one over the other has no drawbacks.

You can also write single-line functions which are called compact functions. In compact functions, each line of code inside the braces is separated with a semicolon (;).

Launch your terminal and write a piece of multiline function. Now press the up arrow key and you will see whatever you wrote in multi-line will be converted as compact functions.

[name](){ first_line; second_line; }
Compact functions
Compact functions

BEST PRACTICE:

  1. Try to choose any one syntax and be consistent with it.
  2. If you are working in a collaborative environment, it is important that everyone maintains the same standard when coding.

Naming convention

When you create a function you have to give a name to your function. The function name should be descriptive and try to avoid names that are already used by other functions, variables, constants, etc. "Snake Case" is the preferred way to name the function. In the snake case, the words are separated by underscores.

Take a look at the below example. I created a function named "hello_world" in snake case style which simply will print out hello world to stdout (Terminal).

hello_world() {
echo "Running Simple Hello World Function"
}

hello_world

How to call a Function in Bash

Let’s create a simple cleanup function named "log_cleanup". The purpose of this function is to remove ".log" files that are older than 30 days.

log_cleanup(){
  echo "Running Cleanup On Older Logs - 30 days"
  find /home/karthick/Documents/Projects/logs/ -name "*.log" -type f -mtime +30 -delete
  echo "Cleanup Completed"
}

The function is defined but will that be enough for the function to do its job? Absolutely not. You have to call the function for the function to be executed.

To call the function, simply type the function name after the function definition:

#!/usr/bin/env bash

#### FUNCTION DEFINITION ####

log_cleanup(){
  echo "Running Cleanup On Older Logs - 30 days"
  find /home/karthick/Documents/Projects/logs -name "*.log" -type f -mtime +30 -delete 
  echo "Cleanup Completed"
}

# Calling the function

log_cleanup  
Call a function in Bash
Call a function in Bash

If you try to call the function before the function definition you will get the following error.

line 3: log_cleanup: command not found
Command not found error
Command not found error

Why so? When you run the script, the code will be interpreted line by line from top to bottom. It will read the function and load it to the bash environment (memory). But here you are calling the function before the interpreter even reads and loads the function to its environment.

When you call a function from inside another function, your function definition can be in any order except the first function. Take a look at the below image. Function func2 is called from func1 and func3 is called from func2 before their definition. But func1 is defined first and then called. By the time func1 is called all the function definitions are already Interpreted and loaded to the environment.

Calling function from another function
Calling function from another function

Exit status & Return value

Every Linux command returns an exit status (0-255). Zero is said to be successful and the rest of the exit codes are said to be failures with different meanings. Similarly, when you run a function it also returns an exit status of the last run command in the function.

Let me run the same "cleanup" function again. But I have given a path that is not available in my machine, so the find command will fail. I am using $? to get the exit status within the script as shown in the image.

Running Cleanup On Older Logs - 30 days
find: '/home/karthick/Documents/Projectss/logs': No such file or directory
Cleanup Completed
Exit status of function log_cleanup is ⇒ 0
Exit status and return value
Exit status and return value

The exit status returned from the function is from the echo command which ran as the last command inside the function. This is not the behavior you might expect.

To overcome this behavior you can use the bash built-in "return" statement.

$ type -a return
return is a shell builtin

Return accepts an integer [N] value and it exits the functions and gives the return value to its caller (function). Before using the return statement, you have to understand a few rules on how to use the return statement. As stated earlier, return accepts integer values from 0-255. A return statement will use the exit status of the last ran command if no argument (Integer value) is passed or the value exceeds 255.

Let me use the return to fix the "cleanup" function behavior. Here I am using conditional statements along with the return command.

#!/usr/bin/env bash

#### FUNCTION DEFINITION ####

log_cleanup(){
 echo "[ INFO ] - Running Cleanup On Older Logs - 30 days"
 if [[ -d "/home/karthick/Documents/Projectss/logs" ]]
 then
   find -name "*.log" -type f -mtime +30 -delete
   echo "[ SUCCESS] - Cleanup Completed"
 else
   echo "[ ERROR ] - Directory path wrong... Cleanup has not happened..."
   return 1
 fi
}

# Calling the function

log_cleanup 
echo "++ Exit status of log_cleanup function is ==> $?"

Take a look at the output below. The function is returning exit code 1 from the return statement.

[ INFO ] - Running Cleanup On Older Logs - 30 days
[ ERROR ] - Directory path wrong… Cleanup has not happened…
++ Exit status of log_cleanup function is ==> 1
Exit status code
Exit status code

Passing arguments to a Function

Similar to passing arguments to your bash scripts, functions accept arguments too. The confusing part is, functions use the same $1$9 special variables to access arguments which is the same as passing the argument to the script. You have to understand what happens when you use this special variable inside and outside the function.

cat > arg_test.sh
#!/bin/bash
echo "Value passed in \$1 is = $1"

howdy(){
   echo "value of \$1 inside function is = $1"
}

howdy # Function Call

Copy and run the above snippet to see the difference. The string "Howdy" is passed as the first argument to the script.

$ ./arg_test.sh howdy
Value passed in $1 is = howdy
value of $1 inside function is =

From the output, you can see $1 inside the function is printing an empty value because $1 inside the function is different from $1 outside the function though they share the same name.

To pass arguments to the function, after the function name leave a space and pass your arguments like as shown in the below image. Each argument separated by space will be assigned with its respective variable $1$N and you can use this variable inside the function to process the arguments.

log_cleanup $1 $2 ….. $N
Passing arguments to a function
Passing arguments to a function

As you see in the above screenshot, I am passing the directory name and number of days as arguments.

Variable scope for a Function

When you create a variable inside the function or outside the function, it can be accessed globally. By default, variables are created in the global scope.

Take a look at the below example. If you try to access both the variable outside_function and inside_function, their values are accessible. This means even though the function ran and exited, the variable created inside the function is accessible globally.

 #!/bin/bash

outside_function="This variable is from outside the function"

func1(){
  inside_function="This variable is from inside the func1"
}

func1
echo $outside_function
echo $inside_function
Global variable
Global variable

In some programming languages, this might not be the behavior and the variables created inside the function will be available during the run time of the function. But in bash, the behavior is different.

To make variables local to the function you can use the bash built-in "local" keyword. The local keyword will restrict the variable scope from global to local and the variable can only be accessed during the function run time.

#!/bin/bash

outside_function="This variable is from outside the function"

func1(){
  local inside_function="This variable is from inside the func1"
}

func1
echo $outside_function
echo $inside_function
Local variable using local keyword
Local variable using local keyword

When the local keyword is used, you can use the same variable names in different functions.

Identical variable names
Identical variable names

Heads Up: You should always try to avoid using identifiers that are already used as variables, functions, bash keywords. The above example is shown just to understand the behavior.

Modularity and Resubaility

Understanding and writing functions can be done in quick time. But writing good functions takes time with a better understanding of the environment. As already pointed out in the introduction section, with bash functions you can achieve a great deal of modularity and reusability.

Let’s take an example. You have created 20 scripts and in each script, you have included the log_cleanup function which we have seen in previous sections to perform housekeeping tasks. Instead of including this function in all 20 scripts you can create a single function definition and import it into 20 scripts. In this way, you are achieving modularity as well as reusable functions. This is similar to import statements in python, include statements in C, etc.

Have a look at the below image. I have created two scripts named script1.sh and script2.sh and the log_cleanup function is written to a separate file named cleanup.sh.

Cleanup function
Cleanup function

I am importing the function by running the source command. The source command will run the file passed as its argument and load the variable or functions to the current bash session. This way when you run log_cleanup from another script file the function is already loaded to the current environment and accessible.

Sourcing functions
Sourcing functions

From the above image, you can understand how the arguments are helpful. There is only one function definition and according to the use case, I can modify the function to accept different arguments and in different scripts.

Heads Up: You can also run your shell scripts with source command which will run the script in the current shell instead of creating a subshell to run the script.

Conclusion

In this guide, we have discussed about Bash functions and how to define and call a function in scripts with examples. To get comfortable with functions, you have to practice the functions with different use cases. If you have any questions or feedback, feel free to let us know via the comment section below.

You May Also Like

1 comment

Motlke September 23, 2021 - 6:48 pm

Very nice post! Thank you for sharing! 🙂

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More