Bash Functions

Creating reusable code blocks in your scripts

⚙️ What are Bash Functions?

Functions are reusable blocks of code that perform specific tasks. They help organize your scripts, reduce repetition, and make code easier to maintain and understand by grouping related commands together.


#!/bin/bash
# Simple function
greet() {
    echo "Hello, World!"
}
greet
                                    

Output:

Hello, World!

Function Concepts

📦

Define

Create a function

function_name() {
    # code
}
▶️

Call

Execute a function

function_name
📥

Parameters

Pass values to functions

function_name arg1 arg2
📤

Return

Send values back

return value

🔹 Creating Basic Functions

Functions group commands into reusable blocks, defined before they're called. Syntax: function_name() { commands; }. The code inside { } executes when the function is invoked by name. Functions must be defined earlier in the script than their calls. They eliminate code duplication, promote modularity, and simplify debugging. For example, a log_msg() function can handle all logging formatting. This encapsulation turns repetitive task sequences into single, manageable units, making scripts shorter, more organized, and easier to update.

#!/bin/bash

# Simple function
say_hello() {
    echo "Hello from a function!"
}

# Function with multiple commands
show_info() {
    echo "System Information:"
    echo "User: $USER"
    echo "Home: $HOME"
    echo "Date: $(date +%Y-%m-%d)"
}

# Function with calculations
calculate_square() {
    num=5
    square=$((num * num))
    echo "Square of $num is $square"
}

# Call the functions
say_hello
echo ""
show_info
echo ""
calculate_square

Output:

Hello from a function!

System Information:
User: john
Home: /home/john
Date: 2025-01-15

Square of 5 is 25

🔹 Functions with Parameters

Functions accept arguments accessed via positional parameters: $1, $2, etc. $@ represents all arguments, and $# gives the count. This allows functions to operate on different data each call. For example, greet() { echo "Hello, $1"; } prints a personalized greeting when called with greet "Alice". Parameters make functions flexible and reusable. Inside, treat them like script arguments. Always validate parameter count (if [ $# -eq 0 ]; then...) to avoid runtime errors and ensure expected behavior.

#!/bin/bash

# Function with one parameter
greet_user() {
    echo "Hello, $1!"
}

# Function with multiple parameters
add_numbers() {
    sum=$(($1 + $2))
    echo "Sum of $1 and $2 is $sum"
}

# Function with parameter count check
show_details() {
    echo "Name: $1"
    echo "Age: $2"
    echo "City: $3"
    echo "Total parameters: $#"
}

# Function with all parameters
print_all() {
    echo "All arguments: $@"
    echo "Number of arguments: $#"
}

# Call functions with arguments
greet_user "Alice"
add_numbers 15 25
echo ""
show_details "John" 30 "New York"
echo ""
print_all apple banana orange grape

Output:

Hello, Alice!
Sum of 15 and 25 is 40

Name: John
Age: 30
City: New York
Total parameters: 3

All arguments: apple banana orange grape
Number of arguments: 4

🔹 Return Values

Functions can exit with a numeric status (0-255) via return or output data via echo. The return statement sets the exit code (0 for success). To "return" a string or computed value, use echo and capture it with command substitution: result=$(my_function). For example, a get_date() { echo $(date); } function outputs the current date. This dual mechanism allows functions to signal both success/failure and provide usable results, integrating seamlessly into larger script logic and pipeline operations.

#!/bin/bash

# Function returning exit status
is_even() {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0  # Success (true)
    else
        return 1  # Failure (false)
    fi
}

# Function returning string via echo
get_greeting() {
    local name=$1
    echo "Welcome, $name!"
}

# Function returning calculation result
multiply() {
    result=$(($1 * $2))
    echo $result
}

# Using return status
is_even 10
if [ $? -eq 0 ]; then
    echo "10 is even"
fi

# Capturing string output
message=$(get_greeting "Bob")
echo "$message"

# Capturing numeric result
product=$(multiply 6 7)
echo "6 x 7 = $product"

Output:

10 is even
Welcome, Bob!
6 x 7 = 42

🔹 Local and Global Variables

Variables in functions are global by default; use local to limit scope. The local keyword creates variables that exist only within their function, preventing side effects and naming collisions with global variables. For example, local count=1 ensures count doesn't alter a global count variable. This practice is crucial for modular, predictable code. Always declare function-specific variables as local unless intentionally sharing state. It enhances encapsulation, readability, and debuggability in complex scripts.

#!/bin/bash

# Global variable
global_var="I am global"

demo_scope() {
    # Local variable
    local local_var="I am local"
    
    # Modify global variable
    global_var="Modified global"
    
    echo "Inside function:"
    echo "  Local: $local_var"
    echo "  Global: $global_var"
}

echo "Before function:"
echo "  Global: $global_var"

demo_scope

echo -e "\nAfter function:"
echo "  Global: $global_var"
echo "  Local: $local_var"  # This will be empty

# Function with local parameters
calculate() {
    local num1=$1
    local num2=$2
    local result=$((num1 + num2))
    echo $result
}

sum=$(calculate 10 20)
echo -e "\nCalculation result: $sum"

Output:

Before function:
  Global: I am global
Inside function:
  Local: I am local
  Global: Modified global

After function:
  Global: Modified global
  Local: 

Calculation result: 30

🔹 Recursive Functions

Recursive functions call themselves to solve problems by breaking them into smaller, similar subproblems. Each iteration moves toward a base case that stops the recursion. In Bash, recursion is useful for directory traversal, calculating factorials, or processing nested structures. For example, a function to list all files in a directory tree can call itself for each subdirectory. Always include a clear termination condition to prevent infinite loops. While Bash isn't optimized for deep recursion due to stack limitations, it's a powerful technique for hierarchical data.

#!/bin/bash

# Factorial function
factorial() {
    local num=$1
    
    # Base case
    if [ $num -le 1 ]; then
        echo 1
    else
        # Recursive case
        local prev=$(factorial $((num - 1)))
        echo $((num * prev))
    fi
}

# Countdown function
countdown() {
    local n=$1
    
    if [ $n -le 0 ]; then
        echo "Blast off!"
    else
        echo $n
        countdown $((n - 1))
    fi
}

# Fibonacci function
fibonacci() {
    local n=$1
    
    if [ $n -le 1 ]; then
        echo $n
    else
        local a=$(fibonacci $((n - 1)))
        local b=$(fibonacci $((n - 2)))
        echo $((a + b))
    fi
}

# Test recursive functions
echo "Factorial of 5:"
result=$(factorial 5)
echo "$result"

echo -e "\nCountdown from 5:"
countdown 5

echo -e "\nFibonacci of 6:"
fib=$(fibonacci 6)
echo "$fib"

Output:

Factorial of 5:
120

Countdown from 5:
5
4
3
2
1
Blast off!

Fibonacci of 6:
8

🔹 Practical Function Examples

Real-world Bash functions encapsulate common operations like logging, validation, and file handling. A die() function can print an error and exit. A is_file_readable() function checks permissions before processing. These reusable blocks make scripts more maintainable and consistent. For instance, a well-designed backup function might handle compression, logging, and cleanup in one call. By building a library of such functions, you accelerate development and ensure reliability across projects. Examples demonstrate how abstract concepts translate into tangible, time-saving tools for automation.

#!/bin/bash

# Check if file exists
file_exists() {
    if [ -f "$1" ]; then
        echo "File '$1' exists"
        return 0
    else
        echo "File '$1' not found"
        return 1
    fi
}

# Create backup
backup_file() {
    local file=$1
    local backup="${file}.backup"
    
    if [ -f "$file" ]; then
        cp "$file" "$backup"
        echo "Backup created: $backup"
    else
        echo "Error: File not found"
    fi
}

# Validate email format
validate_email() {
    local email=$1
    
    if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
        echo "Valid email"
        return 0
    else
        echo "Invalid email"
        return 1
    fi
}

# Display menu
show_menu() {
    echo "=== Main Menu ==="
    echo "1. Option One"
    echo "2. Option Two"
    echo "3. Exit"
    echo "================="
}

# Test functions
file_exists "data.txt"
backup_file "important.txt"
validate_email "[email protected]"
validate_email "invalid-email"
show_menu

Output:

File 'data.txt' exists
Backup created: important.txt.backup
Valid email
Invalid email
=== Main Menu ===
1. Option One
2. Option Two
3. Exit
=================

🧠 Test Your Knowledge

How do you access the first parameter in a Bash function?