Kahibaro
Discord Login Register

2.7.5 Functions

Why Use Functions in Shell Scripts?

Functions let you group a series of commands under a single name so you can:

This chapter focuses on how to define, call, and use functions in shell scripts, especially in bash.

Defining a Function

There are two common ways to define a function in bash:

my_function() {
    echo "Hello from a function"
}

or

function my_function {
    echo "Hello from a function"
}

Both are valid in bash. The first style (name() { ... }) is more portable to other POSIX shells, so it’s generally preferred.

Key points:

Example:

#!/usr/bin/env bash
greet() {
    echo "Welcome to my script!"
}
greet

Where to Place Function Definitions

#!/usr/bin/env bash
say_hello() {
    echo "Hello, $1!"
}
main() {
    say_hello "Alice"
    say_hello "Bob"
}
main

Calling a Function

Calling a function is just like running a command:

greet_user() {
    echo "Hi there!"
}
# Call the function
greet_user

You do not use () when calling:

Passing Arguments to Functions

Functions receive arguments just like scripts do:

Example:

print_greeting() {
    echo "Hello, $1!"
}
print_greeting "Alice"
print_greeting "Bob"

Output:

Hello, Alice!
Hello, Bob!

Using multiple arguments:

show_sum() {
    echo "First number: $1"
    echo "Second number: $2"
    echo "Sum: $(($1 + $2))"
}
show_sum 3 7

Using all arguments:

list_args() {
    echo "You passed $# arguments:"
    for item in "$@"; do
        echo "  - $item"
    done
}
list_args one two "three four"

Note the use of "$@" (quoted) to preserve spaces in arguments.

Return Codes vs Output

In shell, a function can:

  1. Print output using echo (or other commands).
  2. Set an exit status (also called return code) using return.

These are different things:

By convention:

Using `return` to Set Exit Status

Use return inside a function:

is_even() {
    local number="$1"
    if (( number % 2 == 0 )); then
        return 0    # success
    else
        return 1    # failure
    fi
}
is_even 4
echo "Exit status: $?"  # 0
is_even 5
echo "Exit status: $?"  # 1

You then typically check $? or use if directly:

if is_even 10; then
    echo "10 is even"
else
    echo "10 is odd"
fi

Returning Data via Output

Because return only carries a small number code, you return actual data by printing it and (optionally) capturing it:

get_hostname() {
    hostname
}
my_host=$(get_hostname)
echo "This machine is called: $my_host"

Local Variables in Functions

By default, variables in shell are global within the script: a variable set in a function is visible outside it.

To avoid accidental interference, use local inside functions:

counter=0
increment_global() {
    counter=$((counter + 1))
}
increment_local() {
    local counter=0
    counter=$((counter + 1))
    echo "Inside increment_local: counter=$counter"
}
increment_global
echo "After increment_global: counter=$counter"
increment_local
echo "After increment_local: counter=$counter"

Output (approx.):

After increment_global: counter=1
Inside increment_local: counter=1
After increment_local: counter=1

Use local for variables that are meant to be used only within the function:

greet_person() {
    local name="$1"
    echo "Hello, $name!"
}

Function Naming and Organization

Good function names and organization make scripts easier to understand:

Example layout:

#!/usr/bin/env bash
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
backup_file() {
    local file="$1"
    cp "$file" "$file.bak"
    log "Backed up $file"
}
main() {
    backup_file "/etc/hosts"
}
main

Using Functions for Repeated Tasks

Any time you find yourself repeating similar commands, it’s a good candidate for a function.

Without function:

echo "Starting process A..."
# commands for A
echo "Finished process A."
echo "Starting process B..."
# commands for B
echo "Finished process B."

With a function:

run_process() {
    local name="$1"
    echo "Starting process $name..."
    # commands for "$name"
    echo "Finished process $name."
}
run_process "A"
run_process "B"

This reduces duplication and makes later changes easier.

Functions and Exit

Be aware of the difference between return and exit inside a function:

Example:

dangerous() {
    echo "About to exit the whole script!"
    exit 1
}
safe() {
    echo "Leaving this function only."
    return 0
}

In general, prefer return in functions unless you intentionally want to end the whole script.

Simple Debugging of Functions

You can:

Example:

#!/usr/bin/env bash
debug_example() {
    echo "Argument 1: $1"
    echo "Argument 2: $2"
}
debug_example "foo" "bar"

Run the script and check that the output matches your expectations.

Basic Reusable Function Examples

Confirm Action

confirm() {
    local prompt="${1:-Are you sure?} [y/N] "
    read -r -p "$prompt" reply
    case "$reply" in
        [Yy][Ee][Ss]|[Yy]) return 0 ;;
        *)                 return 1 ;;
    esac
}
if confirm "Delete all temporary files?"; then
    echo "Deleting..."
else
    echo "Cancelled."
fi

Check if a Command Exists

command_exists() {
    command -v "$1" >/dev/null 2>&1
}
if command_exists "git"; then
    echo "Git is installed."
else
    echo "Git is not installed."
fi

These small, focused functions are building blocks you can reuse in many scripts.

Views: 73

Comments

Please login to add a comment.

Don't have an account? Register now!