Script Templates
Starting from scratch is slow and error-prone. This lesson provides battle-tested templates you can copy and customize. Each template incorporates the best practices from this moduleβuse them as starting points for your own scripts!
Minimal Script Template
For quick scripts that still need basic safety features.
Output
Click Run to execute your code
When to Use: Small utility scripts, automation tasks, quick prototypes. Add more structure as the script grows.
Full CLI Application Template
For user-facing command-line tools with argument parsing, help, and logging.
#!/usr/bin/env bash
#
# script-name - Brief description of what this script does
#
# Usage: script-name [OPTIONS]
#
# Author: Your Name
# Date: 2024-01-15
# Version: 1.0.0
#
set -euo pipefail
IFS=$'\\n\\t'
# ==============================================================================
# CONSTANTS
# ==============================================================================
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly VERSION="1.0.0"
# Exit codes
readonly EXIT_SUCCESS=0
readonly EXIT_ERROR=1
readonly EXIT_USAGE=2
# ==============================================================================
# DEFAULTS
# ==============================================================================
verbose=false
dry_run=false
config_file=""
log_level="INFO"
# ==============================================================================
# LOGGING
# ==============================================================================
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
log_info() { log "INFO: $*"; }
log_warn() { log "WARN: $*" >&2; }
log_error() { log "ERROR: $*" >&2; }
log_debug() { [[ "$verbose" == "true" ]] && log "DEBUG: $*"; }
die() {
log_error "$*"
exit $EXIT_ERROR
}
# ==============================================================================
# USAGE
# ==============================================================================
show_help() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Brief description of what this script does.
Arguments:
target Description of required argument
Options:
-c, --config FILE Configuration file path
-n, --dry-run Show what would be done
-v, --verbose Enable verbose output
-h, --help Show this help message
--version Show version number
Examples:
$SCRIPT_NAME -v target
$SCRIPT_NAME --config=app.conf target
Environment:
SCRIPT_CONFIG Default config file path
SCRIPT_LOG_LEVEL Log level (DEBUG, INFO, WARN, ERROR)
EOF
}
show_version() {
echo "$SCRIPT_NAME version $VERSION"
}
# ==============================================================================
# ARGUMENT PARSING
# ==============================================================================
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit $EXIT_SUCCESS
;;
--version)
show_version
exit $EXIT_SUCCESS
;;
-v|--verbose)
verbose=true
shift
;;
-n|--dry-run)
dry_run=true
shift
;;
-c|--config)
config_file="$2"
shift 2
;;
--config=*)
config_file="${1#*=}"
shift
;;
--)
shift
break
;;
-*)
die "Unknown option: $1"
;;
*)
break
;;
esac
done
# Remaining args are positional
target="${1:-}"
}
# ==============================================================================
# VALIDATION
# ==============================================================================
validate_args() {
[[ -z "$target" ]] && {
log_error "Missing required argument: target"
echo "Try '$SCRIPT_NAME --help' for usage." >&2
exit $EXIT_USAGE
}
[[ -n "$config_file" && ! -f "$config_file" ]] && {
die "Config file not found: $config_file"
}
}
# ==============================================================================
# CLEANUP
# ==============================================================================
cleanup() {
log_debug "Cleaning up..."
# Add cleanup tasks here
}
trap cleanup EXIT
# ==============================================================================
# MAIN LOGIC
# ==============================================================================
do_work() {
log_info "Processing target: $target"
if [[ "$dry_run" == "true" ]]; then
log_info "[DRY RUN] Would process $target"
return 0
fi
# Main logic here
log_debug "Doing the work..."
log_info "Done!"
}
# ==============================================================================
# ENTRY POINT
# ==============================================================================
main() {
parse_args "$@"
validate_args
do_work
}
# Only run main if executed (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Library/Module Template
For reusable functions that get sourced by other scripts.
#!/usr/bin/env bash
#
# lib-name.sh - Reusable library for [purpose]
#
# Usage: source lib-name.sh
#
# Provides:
# function1 - Description
# function2 - Description
#
# Prevent double-sourcing
[[ -n "${_LIB_NAME_LOADED:-}" ]] && return 0
readonly _LIB_NAME_LOADED=1
# ==============================================================================
# CONFIGURATION
# ==============================================================================
# Library defaults (can be overridden before sourcing)
: "${LIB_DEBUG:=false}"
: "${LIB_LOG_FILE:=}"
# ==============================================================================
# INTERNAL FUNCTIONS
# ==============================================================================
_lib_log() {
[[ "$LIB_DEBUG" == "true" ]] && echo "[lib-name] $*" >&2
}
_lib_validate() {
# Internal validation
:
}
# ==============================================================================
# PUBLIC API
# ==============================================================================
# @description Does something useful
# @param $1 input - The input value
# @return 0 on success, 1 on failure
# @example
# result=$(lib_function1 "input")
lib_function1() {
local input="${1:?Usage: lib_function1 }"
_lib_log "Processing: $input"
# Implementation
echo "Processed: $input"
return 0
}
# @description Another useful function
# @param $1 arg1 - First argument
# @param $2 arg2 - Second argument (optional)
lib_function2() {
local arg1="$1"
local arg2="${2:-default}"
_lib_log "Args: $arg1, $arg2"
# Implementation
return 0
}
# ==============================================================================
# INITIALIZATION
# ==============================================================================
_lib_validate
# Self-test when run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "Running lib-name.sh self-test..."
LIB_DEBUG=true
echo "Testing lib_function1:"
lib_function1 "test input"
echo "Testing lib_function2:"
lib_function2 "arg1" "arg2"
echo "All tests passed!"
fi
Service/Daemon Template
For long-running background scripts with signal handling.
#!/usr/bin/env bash
#
# service-name - Long-running service script
#
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly PID_FILE="/var/run/${SCRIPT_NAME}.pid"
readonly LOG_FILE="/var/log/${SCRIPT_NAME}.log"
running=true
# ==============================================================================
# LOGGING
# ==============================================================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$$] $*" | tee -a "$LOG_FILE"
}
# ==============================================================================
# SIGNAL HANDLERS
# ==============================================================================
handle_shutdown() {
log "Received shutdown signal"
running=false
}
handle_reload() {
log "Reloading configuration..."
# Reload logic here
}
trap handle_shutdown SIGTERM SIGINT
trap handle_reload SIGHUP
# ==============================================================================
# PID FILE MANAGEMENT
# ==============================================================================
create_pid_file() {
echo $$ > "$PID_FILE"
log "Created PID file: $PID_FILE"
}
remove_pid_file() {
rm -f "$PID_FILE"
log "Removed PID file"
}
check_already_running() {
if [[ -f "$PID_FILE" ]]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
log "Already running with PID $pid"
exit 1
fi
log "Removing stale PID file"
rm -f "$PID_FILE"
fi
}
# ==============================================================================
# MAIN LOOP
# ==============================================================================
do_work() {
# Your service logic here
log "Doing periodic work..."
sleep 1
}
main() {
check_already_running
create_pid_file
trap remove_pid_file EXIT
log "Service started"
while [[ "$running" == "true" ]]; do
do_work
done
log "Service stopped"
}
main "$@"
Template Comparison
| Template | Best For | Features |
|---|---|---|
| Minimal | Quick scripts, prototypes | Error handling, cleanup |
| CLI | User-facing tools | Args, help, logging, validation |
| Library | Reusable functions | Sourcing guard, documentation |
| Service | Background processes | Signals, PID file, main loop |
Summary
- Start with templates: Don't reinvent the wheel
- Minimal: Quick tasks with basic safety
- CLI: Full-featured command-line tools
- Library: Reusable code with source guard
- Service: Long-running with signal handling
- Customize: Add/remove features as needed
What's Next?
Now let's learn about Testing Scripts. You'll discover strategies for testing Bash scripts to ensure they work correctly!
Enjoying these tutorials?