Function Basics
Functions are reusable blocks of code that help organize your scripts, reduce repetition, and make your code more maintainable. They allow you to break complex scripts into smaller, manageable pieces.
What are Functions?
Functions in Bash are named blocks of code that can be called multiple times throughout your script. They help you:
- Organize code: Break large scripts into logical pieces
- Reduce repetition: Write once, use many times
- Improve readability: Make scripts easier to understand
- Enable testing: Test individual components
- Facilitate maintenance: Update code in one place
Function Syntax
Method 1: Using the function keyword
function function_name() {
# function body
commands
}Method 2: POSIX-compatible syntax (preferred)
function_name() {
# function body
commands
}Basic Function Examples
Simple Function
#!/bin/bash
# Define a function
greet() {
echo "Hello, World!"
}
# Call the function
greetFunction with Commands
#!/bin/bash
show_date() {
echo "Current date and time:"
date
echo "Uptime:"
uptime
}
# Call the function
show_dateFunction Parameters
Functions can accept parameters (arguments) to make them more flexible:
Accessing Parameters
#!/bin/bash
greet_user() {
echo "Hello, $1!"
echo "Welcome to $2"
}
# Call function with arguments
greet_user "Alice" "Bash scripting"Multiple Parameters
#!/bin/bash
user_info() {
local name="$1"
local age="$2"
local city="$3"
echo "Name: $name"
echo "Age: $age"
echo "City: $city"
}
# Call with multiple arguments
user_info "John Doe" "30" "New York"Parameter Variables
#!/bin/bash
show_params() {
echo "Function name: $0"
echo "First parameter: $1"
echo "Second parameter: $2"
echo "All parameters: $@"
echo "Number of parameters: $#"
echo "All parameters as single string: $*"
}
show_params "arg1" "arg2" "arg3"Return Values
Functions can return values using the return statement:
Return Exit Status
#!/bin/bash
check_file() {
local filename="$1"
if [ -f "$filename" ]; then
echo "File $filename exists"
return 0 # Success
else
echo "File $filename does not exist"
return 1 # Failure
fi
}
# Use function and check return value
if check_file "test.txt"; then
echo "File check passed"
else
echo "File check failed"
fiReturn Values via Echo
#!/bin/bash
get_file_size() {
local filename="$1"
if [ -f "$filename" ]; then
stat -f%z "$filename" 2>/dev/null || stat -c%s "$filename" 2>/dev/null
else
echo "0"
fi
}
# Capture function output
file_size=$(get_file_size "test.txt")
echo "File size: $file_size bytes"Local Variables
Use local to create variables that exist only within the function:
Without Local Variables (Global)
#!/bin/bash
global_example() {
name="Alice" # This modifies global variable
echo "Inside function: $name"
}
name="Bob"
echo "Before function: $name"
global_example
echo "After function: $name" # Changed to AliceWith Local Variables
#!/bin/bash
local_example() {
local name="Alice" # This is local to the function
echo "Inside function: $name"
}
name="Bob"
echo "Before function: $name"
local_example
echo "After function: $name" # Still BobBest Practice Example
#!/bin/bash
calculate_area() {
local length="$1"
local width="$2"
local area
area=$((length * width))
echo "$area"
}
# Use the function
room_area=$(calculate_area 10 12)
echo "Room area: $room_area square feet"Practical Function Examples
File Operations Function
#!/bin/bash
backup_file() {
local source_file="$1"
local backup_dir="${2:-./backup}"
# Validate input
if [ -z "$source_file" ]; then
echo "Error: Source file not specified"
return 1
fi
if [ ! -f "$source_file" ]; then
echo "Error: Source file '$source_file' does not exist"
return 1
fi
# Create backup directory if it doesn't exist
mkdir -p "$backup_dir"
# Create backup with timestamp
local backup_name="$(basename "$source_file").backup.$(date +%Y%m%d_%H%M%S)"
if cp "$source_file" "$backup_dir/$backup_name"; then
echo "Backup created: $backup_dir/$backup_name"
return 0
else
echo "Error: Failed to create backup"
return 1
fi
}
# Use the function
backup_file "/etc/hosts"
backup_file "important.txt" "/safe/location"System Information Function
#!/bin/bash
show_system_info() {
local info_type="$1"
case "$info_type" in
"cpu")
echo "CPU Information:"
lscpu | grep "Model name" || echo "CPU info not available"
;;
"memory")
echo "Memory Information:"
free -h
;;
"disk")
echo "Disk Usage:"
df -h
;;
"all")
show_system_info "cpu"
echo
show_system_info "memory"
echo
show_system_info "disk"
;;
*)
echo "Usage: show_system_info {cpu|memory|disk|all}"
return 1
;;
esac
}
# Use the function
show_system_info "all"String Processing Function
#!/bin/bash
process_string() {
local input="$1"
local operation="$2"
case "$operation" in
"upper")
echo "${input^^}"
;;
"lower")
echo "${input,,}"
;;
"length")
echo "${#input}"
;;
"reverse")
echo "$input" | rev
;;
"words")
echo "$input" | wc -w
;;
*)
echo "Usage: process_string <string> {upper|lower|length|reverse|words}"
return 1
;;
esac
}
# Use the function
text="Hello World"
echo "Original: $text"
echo "Uppercase: $(process_string "$text" "upper")"
echo "Lowercase: $(process_string "$text" "lower")"
echo "Length: $(process_string "$text" "length")"
echo "Reversed: $(process_string "$text" "reverse")"
echo "Word count: $(process_string "$text" "words")"Function Libraries
You can create reusable function libraries:
Create a Library File (utils.sh)
#!/bin/bash
# utils.sh - Utility functions library
# Logging function
log() {
local level="$1"
shift
local message="$*"
echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message"
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Validate email format
is_valid_email() {
local email="$1"
[[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}
# Get file extension
get_extension() {
local filename="$1"
echo "${filename##*.}"
}
# Create directory if it doesn't exist
ensure_directory() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
log "INFO" "Created directory: $dir"
fi
}Use the Library in Your Script
#!/bin/bash
# main_script.sh
# Source the utility functions
source ./utils.sh
# Use the functions
log "INFO" "Script started"
if command_exists "git"; then
log "INFO" "Git is available"
else
log "ERROR" "Git is not installed"
exit 1
fi
email="user@example.com"
if is_valid_email "$email"; then
log "INFO" "Email format is valid"
else
log "ERROR" "Invalid email format"
fi
filename="document.pdf"
extension=$(get_extension "$filename")
log "INFO" "File extension: $extension"
ensure_directory "/tmp/myapp"
log "INFO" "Script completed"Function Best Practices
1. Use Descriptive Names
# Good
calculate_monthly_payment() {
# function body
}
# Bad
calc() {
# function body
}2. Validate Input Parameters
backup_database() {
local db_name="$1"
local backup_path="$2"
# Validate required parameters
if [ -z "$db_name" ]; then
echo "Error: Database name is required"
return 1
fi
if [ -z "$backup_path" ]; then
echo "Error: Backup path is required"
return 1
fi
# Function logic here
}3. Use Local Variables
process_data() {
local input_file="$1"
local output_file="$2"
local temp_file
local line_count
# All variables are local to this function
temp_file=$(mktemp)
line_count=$(wc -l < "$input_file")
# Process data...
}4. Provide Usage Information
convert_temperature() {
local temp="$1"
local from_unit="$2"
local to_unit="$3"
if [ $# -ne 3 ]; then
echo "Usage: convert_temperature <temperature> <from_unit> <to_unit>"
echo "Units: C (Celsius), F (Fahrenheit), K (Kelvin)"
return 1
fi
# Conversion logic here
}5. Handle Errors Gracefully
download_file() {
local url="$1"
local destination="$2"
if ! command -v curl >/dev/null 2>&1; then
echo "Error: curl is not installed"
return 1
fi
if ! curl -o "$destination" "$url"; then
echo "Error: Failed to download $url"
return 1
fi
echo "Successfully downloaded to $destination"
return 0
}Common Function Patterns
Configuration Function
load_config() {
local config_file="${1:-config.conf}"
if [ -f "$config_file" ]; then
source "$config_file"
echo "Configuration loaded from $config_file"
else
echo "Warning: Configuration file $config_file not found"
echo "Using default settings"
fi
}Cleanup Function
cleanup() {
local temp_files=("$@")
echo "Cleaning up temporary files..."
for file in "${temp_files[@]}"; do
if [ -f "$file" ]; then
rm "$file"
echo "Removed: $file"
fi
done
}
# Set trap to call cleanup on exit
trap 'cleanup "$temp_file1" "$temp_file2"' EXIT