In Bash, functions are a powerful way to modularize your code and make it more readable. Functions can also be used to create reusable code blocks. One of the most common uses for functions is to create custom commands. For example, you might want to create a command that prints the contents of a file. You could do this by creating a function that takes an input parameter—in this case, the file name—and prints out the contents of the file. You can also use functions to modularize your code. For example, you might have a block of code that performs several tasks, such as printing out a list of files or listing all the users in your system. You could break this block of code into separate functions and then call each one from within your main program. This makes your program more organized and easier to read. Local variables are another feature that you can use in functions. Local variables are variables that are local to the function itself; they don’t exist outside of the function scope. This means that you can access them without having to declare them first—you just use their names as parameters when you call the function. This makes it easy to keep track of which variables are being used in which parts of your function body. ..

What Are Bash Functions?

Alike to other coding languages, Bash allows you to create functions from within your code or script. A function can be defined to do a specific task, or set of tasks, and can be called easily and readily from within your main code by simply using the name given to the function.

You can also nest function calls (calling a function from within another function), use local variables within the function and even pass variables back and forth with functions, or via using global variables. Let’s explore.

Simple Bash Function

We define a test.sh with our text editor, as follows:

Subsequently, we make that file executable by adding the execute (x) property, and execute the script:

We can see how the script first defines a welcome() function using the Bash idioms function_name(), and {…} function wrappers. Finally, we call the function welcome by simply using it’s name, welcome.

When the script is executed, what happens in the background, is that the function definition is noted, but skipped (i.e. not executed), until a bit lower the function call welcome is hit, and which point the Bash interpreter executes the welcome function and returns to the line directly after the function calls afterwards, which in this case is the end of our script.

Passing variables to Bash functions

We define a test2.sh with our favorite text editor (vi ;), as follows:

We again make our script executable by using chmod +x test2.sh and execute the same.

The resulting output may look interesting, or even confusing at first. However, it is logical and easy to follow. The fist option passed to the script will, globally, be available from within the code as ${1}, except inside functions, where ${1} becomes the first parameter passed to the function, ${2} the second etc.

In other words, the global ${1} variable (the first option passed to the script from the command line) is not available from within functions, where the meaning of the ${1} variable changes to the first option passed to the function. Think hierarchy, or think about how a function could present a small script by itself and this will soon make sense.

As a sidenote, one could also use $1 instead of ${1}, but I strongly encourage aspiring Bash coders to always surround variable names with { and }.

The reason is that sometimes, when using variables in a string for example, the Bash interpreter is not able to see where a variable ends and part of the adjoining text may be taken to be part of the variable name where it is not, leading to unexpected output. It is also cleaner and clearer what the intention is, especially when it comes to arrays and special option flags.

We thus start our program with the global ${1} variable set to “first”. If you look at the calling of func1, you will see that we pass that variable onto the function, thus the ${1} inside the function becomes whatever was in the ${1} of the program, i.e. “first”, and this why the first line of output is indeed first.

We then call func2 and we pass two strings “a” and “b” to the function. These then become the ${1} and ${2} automatically inside the func2 function. Inside the function, we print them in reverse, and our output matches nicely with b a as the second line of output.

Finally, we also do a check at the top of our script which ensures that an option is actually passed to the test2.sh script by checking if “${1}” is empty or not using the -z test inside the if command. We exit the script with a non-zero exit code (exit 1) to indicate to any calling programs that something went amiss.

Local variables and returning values

For our final example, we define a test3.sh script as follows:

We again make it executable and execute the script. The output is cba as can be expected by scanning over the code and noting the variable names etc.

However, the code is complex and takes a little getting used to. Let’s explore.

First, we define a function func3 in which we create a local variable named REVERSE. We assign a value to it by calling a subshell ($()), and from within this subshell we echo whatever was passed to the function (${1}) and pipe this output to the rev command.

The rev command prints the input received from the pipe (or otherwise) in reverse. Also interesting to note here is that the ${1} variable remains inside the subshell! It is past integrally.

Next, still from within the func3 function, we print the output. However, this output will not be sent to the screen, it rather will be captured by our function call itself and thus stored within the ‘global’ REVERSE variable.

We set our input to “abc”, call the func3 function again from within a subshell, passing the INPUT variable, and assign the output to the REVERSE variable. Note that there is absolutely no connection between the ‘global’ REVERSE variable and the local REVERSE variable inside the script.

Whilst any global variable, including any REVERSE will be passed to the function, as soon as a local variable is defined with the same name, the local variable will be used. We can also prove and see this another small script test4.sh:

When executed the output is cba and test. The cba is this time generated by the same echo “${REVERSE}” inside the func3 function, but is this time output directly instead of captured in the code below as the func3 “${INPUT}” function is not called from within a subshell.

This script highlights two learning points we covered earlier: firstly, that – even though we set the REVERSE variable to “test” inside the script before calling the func3 function – that the local variable REVERSE takes over and is used instead of the ‘global’ one.

Secondly, that our ‘global’ REVERSE variable retains it value even though there was a local variable with he same name used from within the called function func3.

Wrapping Up

As you can see, Bash functions, the passing of variables, as well as the use of local and semi-global variables makes the Bash scripting language versatile, easy to code, and gives you the possibility to define well structured code.

Also noteworthy to mention here is that besides improved code readability and easy-of-use, using local variables provides additional security as variables will not be accessible outside of the context of a function etc. Enjoy functions and local variables whilst coding in Bash!

If you’re interested in learning more about Bash, checkout our How to Correctly Parse File Names in Bash, and Using xargs in Combination With bash -c to Create Complex Commands.