8.4. Function Libraries#
8.4.1. Common Pitfalls#
1. Global variables polluting the namespace
# Bad: global variables in library
counter=0
increment() {
counter=$((counter + 1))
}
# Good: use local or prefixed globals
inc_counter=0
increment() {
inc_counter=$((inc_counter + 1))
}
2. Hardcoded paths instead of script-relative paths
# Bad: breaks if script location changes
source /home/user/lib_utils.sh
# Good: relative to script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib_utils.sh"
3. Not handling sourcing errors
# Bad: silently fails
source "$SCRIPT_DIR/lib_utils.sh"
my_function # Error: command not found if sourcing failed
# Good: check success
source "$SCRIPT_DIR/lib_utils.sh" || {
echo "Failed to load library"
exit 1
}
my_function
8.4.2. Real-World Example: Complete Library System#
Create a file lib_deploy.sh:
#!/bin/bash
# lib_deploy.sh - Deployment utilities
readonly LOG_FILE="/var/log/deploy.log"
# Log deployment events
deploy_log() {
local msg="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $msg" >> "$LOG_FILE"
}
# Check deployment prerequisites
check_prerequisites() {
local required=("git" "docker" "kubectl")
for cmd in "${required[@]}"; do
if ! command -v "$cmd" > /dev/null; then
deploy_log "ERROR: $cmd not found"
return 1
fi
done
deploy_log "All prerequisites met"
return 0
}
# Deploy Docker image
deploy_image() {
local image=$1
local tag=$2
deploy_log "Deploying $image:$tag"
docker pull "$image:$tag" || return 1
kubectl set image deployment/app app="$image:$tag" || return 1
deploy_log "Successfully deployed $image:$tag"
return 0
}
Usage script deploy.sh:
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib_deploy.sh"
if ! check_prerequisites; then
echo "Prerequisites missing"
exit 1
fi
deploy_image "myapp" "v1.2.3"
8.4.3. Library Design Best Practices#
8.4.3.1. 1. Namespace Convention#
Prefix function names to avoid conflicts:
# lib_string.sh
string_uppercase() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
string_lowercase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
# lib_file.sh
file_exists() {
[[ -f "$1" ]]
}
file_is_readable() {
[[ -r "$1" ]]
}
8.4.3.2. 2. Document Function Purpose#
# Validate an email address (basic regex)
# Args: $1 = email to validate
# Returns: 0 if valid, 1 if invalid
validate_email() {
local email=$1
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
return 0
else
return 1
fi
}
8.4.3.3. 3. Use Local Variables#
All function variables should be local to prevent pollution:
# lib_math.sh
math_factorial() {
local n=$1
local result=1
local i
for ((i = 2; i <= n; i++)); do
result=$((result * i))
done
echo $result
}
8.4.4. Handling Relative Paths in Libraries#
When sourcing libraries, use a technique to find them relative to the script location.
8.4.4.1. Robust Library Sourcing#
#!/bin/bash
# myscript.sh
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source the library relative to script location
source "$SCRIPT_DIR/lib_utils.sh"
# Now functions are available
log "Script started"
BASH_SOURCE[0] contains the path of the script being executed, allowing you to find related libraries in the same directory.
8.4.5. Sourcing Libraries with source or .#
Use the source command (or .) to load functions from another file.
8.4.5.1. Sourcing a Library#
#!/bin/bash
# myscript.sh
# Load the library
source ./lib_utils.sh
# Or use the dot notation (same thing)
# . ./lib_utils.sh
# Now use functions from the library
if is_root; then
log "Running as root"
else
error "This script requires root privileges"
exit 1
fi
if command_exists "docker"; then
log "Docker is installed"
fi
Important: When sourcing, use a path. source lib_utils.sh looks in PATH; source ./lib_utils.sh uses current directory.
8.4.6. Creating a Function Library#
A function library (or library script) is a file containing a collection of related functions, designed to be used by multiple scripts.
8.4.6.1. Library File Structure#
Create a file like lib_utils.sh:
#!/bin/bash
# lib_utils.sh - Common utility functions
# Function to check if a command exists
command_exists() {
command -v "$1" > /dev/null 2>&1
}
# Function to check if user is root
is_root() {
[[ $EUID -eq 0 ]]
}
# Function to log messages with timestamp
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
# Function to print error messages
error() {
echo "ERROR: $*" >&2
}
Best practice: Include a shebang and add comments documenting each function.