Bash Bonanza Part 3: Functions

Welcome to the third part of the Bash Bonanza series!

If you have been following the previous parts, you'll know what we previously covered why single quotes are awesome, and how to safely use double quotes if you have to.

We're now in a good place to cover bash functions and how to return variables from them.

Function Syntax

Functions in bash are written and called using the following syntax:

Input

#!/usr/bin/env bash

# Function definition
function_name() {  
    echo "Hello World!"
}

# Function call
function_name  

Output

Hello World!  
Passing Variables to Functions

For functions to be useful, they need to accept parameters. This is fairly simple - to pass arguments to a function you simply write them next to the function call. These will then be available inside the function by $argument_number - for example $1 for the first argument.

tasty_thing="Pineapples"  
function_name 'Hello World!' "$tasty_thing"  

Note that if your argument has white space in it, bash will interpret it as two separate arguments! That is why we are using 'Hello World!' as opposed to Hello World!.

It would also be ideal if we could scope the parameters, as by default, bash variables are global. This would quickly become a problem for any reasonably sized bash script. To accomplish this we can use the local builtin command.

An example script of what we have learned so far:
Input

#!/usr/bin/env bash

# $1 - greeting
# $2 - message to the world
greet_world() {  
    # Note: shift will shift the arguments down, so $2 becomes $1
    # This is a personal preference - you don't have to use it
    local greeting="$1"; shift
    local message="$1"; shift

    echo "$greeting World!"
    echo "$message"
}

greeting="Aloha"  
greet_world "$greeting" 'I ate a large pineapple today!'  

Output

Aloha World!  
I ate a large pineapple today!  
Returning Variables

Finally, there is the issue of returning values from a function.

One way is to call the function in a subshell and then parse the return value. For example, in the function above, we would do the following:

response=$(greet_world "$greeting" 'I ate a large pineapple today!')  
# parse response however you want

This has a few disadvantages though:

  • You are creating a subshell for a function call
  • If your function encounters an error, only the subshell would exit with an error, not your current script
  • You may want concrete (or multiple) return values as opposed to having to parse from the standard output

To avoid these problems, I use a small helper function, which I admittedly stole from stack overflow a while ago and can no longer attribute - shout out if you know who it is so I can credit!

# $1 - variable name
# $2 - variable value
function indirect-string-assignment() {  
    # INPUT
    local variable_name="$1"; shift
    local variable_value="$1"; shift

    unset -v "$variable_name" || echo "Invalid identifier: $variable_name" >&2
    printf -v "$variable_name" '%s' "$variable_value"
}

This function will take a variable name, unset it, and assign it to the same variable name again, which will be one that was previously defined. The usage in the script below should make it a bit clearer:

Input

#!/usr/bin/env bash

# INPUT
# $1 - variable name
# $2 - variable value
function indirect-string-assignment() {  
    local variable_name="$1"; shift
    local variable_value="$1"; shift

    unset -v "$variable_name" || echo "Invalid identifier: $variable_name" >&2
    printf -v "$variable_name" '%s' "$variable_value"
}

# INPUT
# $1 - greeting
# $2 - message to the world

# OUTPUT
# $3 - variable name that will hold a greeting from the world
# $4 - variable name that will hold a reply to your message from the world
greet_world() {  
    # INPUT
    local greeting="$1"; shift
    local message="$1"; shift

    # OUTPUT
    local return_greeting_variable_name="$1"; shift
    local return_reply_variable_name="$1"; shift

    echo "$greeting World!"
    echo "$message"

    # create ANOTHER local variable with the same name as the one in main
    # indirect-string-assignment will then unset THIS local variable, and set the response value on the next one down, which is the local variable in main
    # this works because since greet_world was called from main, it actually has access to all of main's local variables
    local "$return_greeting_variable_name" && indirect-string-assignment "$return_greeting_variable_name" "${greeting}!"
    local "$return_reply_variable_name" && indirect-string-assignment "$return_reply_variable_name" 'That'"'"'s great!'
}

main() {  
    local greeting="Aloha"
    # create local world_greeting and world_reply and pass the variable NAMES to the greet_world function
    local world_greeting && local world_reply && greet_world "$greeting" 'I ate a large pineapple today!' 'world_greeting' 'world_reply'

    echo "$world_greeting"
    echo "$world_reply"
}

main  

Output

Aloha World!  
I ate a large pineapple today!  
Aloha!  
That's great!  
Caveats

What we have discussed so far will only work for variables that contain strings. Unfortunately, if you start using arrays, things will not quite work as you expect.

In the next part of the series, we will explore bash arrays and how to pass them around functions - stay posted!



Try Cloud 66 for Free, No credit card required