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 =================