Functions

Functions are a blocks of code that are executed only when called. The purpose of the functions is to break program into smaller and modular chunks that are easy to maintain, reduce unnecessary repeat and that way increase the reusabality.
Functions must be defined before you can call them and often some kind of data is passed to a function as an agrument to perform desired action with given values. When a function has performed it's action, a return code or exit status is returned.
Whenever you need to execute the same actions multiple times in a script, a function might be a good choise. So instead of writing the same code all over again, just call a function when needed to perform desired action.

Creating a function

There are two possible formats what can be used to create a function. Formats are pretty much the same, except the keyword function is used on other.
The first syntax uses a function name followed by a set of paranthesis and a open curly brace. The commands that follow the curly braces will be executed when the function is called. At end of function, a curly brace must be used to end the block. The second syntax starts with a keyword function followed by a function name. This syntax doesn't need parenthesis. Everything else is the same as in the other syntax. There are no advantages between the meantioned syntaxes, it's just a personal preference which one you want to use.

NOTE: In other programming languages, possible parameters that can be taken are listed between parenthesis (). This ISN'T the case with a shell scripts, you never put anything inside them!

#!/bin/bash

#1st format
function_name() {
  <commands>
}

#2nd format
function function_name {
  <commands>
}


Calling a function

In order to call a function, simply type the name of the function on a line where you want to call it, and it will be executed.
When calling a function, do not use parenthesis (()) like in other programming languages!

#!/bin/bash

print_welcome () { #define a function by giving a name
  echo "Welcome!"  #define commands between curly braces
}

print_welcome #call a function 1st time
print_welcome #call a function 2nd time

#output:
#Welcome!
#Welcome!

Remember that the functions can call other functions as well! Like mentioned earlier, a function must be defined before calling. The same principle applies to the calling other functions inside of a function.

Lets take an example:

#!/bin/bash

print_welcome () {
  echo "Welcome!"
  print_username
}

#print_welcome
#above call would fail!

print_username () {
  whoami
}

print_welcome

#output:
#Welcome!
#user

The following example works fine, why?
On execution wise, it's defined before it is used. print_welcome function was defined after the print_welcome function, but the print_username actually gets read into the script before the print_welcome function is called.
If you uncomment a function call between 2 function deifinations, you would get an error print_username not found because welcome function get executed before username function definition.
This is a case in all scripting languages because they are not pre-compiled. All components and commands are read procedurally, from top to bottom. Thats why it's a good practice to define all your functions at the top of the script.


Passing arguments

Usually a function should process some data passed to it. Data can be given a function the same way as a commandline arguments were given at the script startup, supplying arguments directly after the function call.
The naming conventions are also the same, the same positional parameters are used.

  • $0 = Script name
  • $1 = 1st given argument
  • $2 = 2nd given argument
  • $@ or "$@" = All arguments given (quotes remove a word splitting functionality inside a single item)
  • $# = Number of arguments given
  • etc.

Return values

As a JAMK student, return values concept should be familiar to you. In general, it means that function send data back to the calling location. This is not the case with functions in shell scripts!.
Shell functions can only return a status which indicates whether it succeeded or not. This status can be called as exit status or as a return code. You can think this the same way as a exit status as normal Linux command output, does it succeeded or not.

User can explicitly set a custom exit status by using a return command followed with a wanted status. If no return statement is defined, function returns the exit status of the last executed command.
The return statement only accepts integer values, number from 0 to 255. Exit code 0 indicates the successful execution of a command/function. A non-zero code indicates some kind of error.
You can use these exit codes to make a decisions. For example with a if statement, 0 --> everything went fine move on, something else --> some error handling.

Exit statuses and return codes are introduced move detailed on the next chapter!

#!/bin/bash

print_string () {
  echo "String was: ${1}"
  return 100
}

print_string test
echo "Function returned ${?}"

#output:
#String was: test
#Function returned 100

Variable scope

A term variable scope refers to which parts of the script can see which variables. By default, all variables are global. This means that the variable and it's value are visible and accesible anywhere in the script. The only catch was that variable must be defined before it can be used. Function cannot use a global variable if it's defined after a function call.
Remember that if you declare a new variable inside a function, it automatically comes a global variable as well but it's not available until a function is called.
A local variable is a variable type that can only be accessed with the function where it was declared. A local variable is declared like a normal variable but with a local keyword before a variable name. Also note that local keyword cannot be used outside of a function!
It's a good practice to use local variables inside function, to be sure that no undesired value overwrites happen that might cause problems. Usually you are fine if you use unique variable names tho.

Syntax: local variable_name=<value>

#!/bin/bash

# define a function with a variable definition inside
example () {
  GLOBAL_VARIABLE="ounou"
}

#global variable not available yet
echo ${GLOBAL_VARIABLE}

#function call
example

#global variable is now available
echo ${GLOBAL_VARIABLE}

#output:
# <empty line>
#ounou

#!/bin/bash

change_value () {
  local variable1="local value"
  echo "Function values: ${variable1} : ${variable2}"
  variable1='1 value changed by a function'
  variable2='2 value changed by a function' 
}

variable1='global 1'
variable2='global 2'

echo "Before function call: ${variable1} : ${variable2}"

change_value

echo "After function call: ${variable1} : ${variable2}"


#output:
#Before function call: global 1 : global 2
#Function values: local value : global 2
#After function call: global 1 : 2 value changed by a function