Function

POSIX shell supports function as a feature. Function is a way of separating or group a list of executions, similar to any other programming languages. It itself is considered an "object" like the variables. Function is very useful for:

  • Simplifying main body of codes
  • Promotes reusability and modularity
  • Making codes testable

Defining Function

Function is defined by a no-spacing name with the parenthesis (()) right after, following by either a curly bracket ({}) or a parenthesis containing the function contents. Depending on needs, curly bracket indicates that the function execute sequentially in a script while the parenthesis forks out a new process to execute the function in parallel.


Sequential Execution Function Format

To make sure the function executes sequentially without forking out a new process, the format is as follows:

function_name() {
        ...
}

The contents of the function are usually tabbed by 1 time for readability purposes.


Parallel Execution Function Format

To make sure the function executes in a separate process (or we called in parallel processing), the format is as follows:

function_name() (
        ...
)

The contents of the function are usually tabbed by 1 time for readability purposes.

NOTE:

Keep in mind that calling this function will create a child process (sub-shell) executing the function, not independently. Hence, you should be very careful with:

  1. Return value
  2. Providing input between the main script and the function
  3. Any changes in the function is not carried forward to the parent process (main script)
  4. Plan and regulate the exit path for the child process (function)

Exporting Functions

In POSIX shell, there is only one way to export: using source command.

POSIX Compatible Export

Basically write your functions in a separate scripts like a library. Then in your main script, source the script. Example:

# in path/to/library-script
function_name() {
        ...
}

# in main script
source "path/to/library-script"
function_name

This technique is widely used across Linux and Unix system such as your $HOME/.bashrc. The compromise is that you have to write separate scripts.

Return Value and Code

Similar to any programs in Linux/Unix environment, function returns code and value depending on implementations. We will look into it in details.


Return Code

All function returns integer code (0-255). That's it. As a standard practice, 0 means successful while non-0 means error in place depending on your definition. Hence, a function is:

function_name() {
        ...
        return 0
}

function_name
return_code=$?

Return code is captured once after the execution using $? special variable. Hence, be sure to capture and save it into a variable before losing it.


Return Value

Functions does not return value (apart of integer) directly. Instead, it relies on "silent is gold" practices to return a value. In another word, function uses standard output (stdout) and standard error (stderr) extensively to generate the necessary output alongside with return code. The practices are:

  1. Only echo the desired output into stdout. If there is no output, be quiet.
  2. Only echo status or error messages into stderr so that it is not recognized as an output.

Here's an example:

function_name() {
        1>&2 echo -n "This function is called."         # status
        1>&1 echo "Julia"                               # output
        return 0
}

return_value=$(function_name)
return_code=$? 

Another way is to create a variable up for the function to set as an output value. Here is an example:

return_value=""
function_name() {
        1>&2 echo -n "This function is called."         # status
        return_value="Julia"                            # output
        return 0
}

function_name
return_code=$?

This method works for sequential execution functions within the same script; it is not for parallel execution functions.

Recursive Function

Similar to any other programming languages, POSIX shell allows you to create recursive functions. Here's an example:

function_r() {
        i="$1"
        if [ $i -eq 25 ]; then
                return 0
        fi

        echo "Now: $i"
        ...

        i="$((i + 1))"
        function_r "$i"
}

NOTE:

  • Remember to design the return trigger first before writing the contents. A bad recursive function can fatally crash the program by segmentation memory starvation.

Parameters

Function uses the same argument system like calling any program in Linux or Unix. Unlike any programming languages, POSIX shell function doesn't needs to be descriptive or be declared. However, it is your duty to parse all the arguments properly using the special arguments like $@, $0, $1, $2, and goes on. Here's an example:


Self-defined Position Arguments

You can make use of comments to describe the positional arguments. However, you must perform the necessary parameter input before using them. A good practice is to validate the argument with only the usable value.

# $1 - verdict. Accepts only: true, false 
function_name() {
        if [ "$1" != "true" || "$1" != "false" ]; then
                1>&2 echo "[ ERROR ] wrong value for argument 1"
                return 1
        fi
        ...
}


Argument Processing

Another method is to process all the arguments using $@ or $#. This allows you to use flag system to provide input. The caveat is that the processing can be lengthy and a lot of codes. Here's an example:

function_name() {
        # argument processing
        while [ $# -ne 0 ]; do
        case "$1" in
        -v|--version)
                version="$1"
                ;;
        *)
                ;;
        esac
        shift
        done

        # function body
        ...
}

That's all about POSIX functions. Feel free to make your shell codes modular with it.