Script Structure: The Professional Template
Every good script follows a structure. Learn it once. Use it forever.
Here's the thing: Structure makes scripts readable. Maintainable. Professional. Use it.
The Basic Structure
#!/bin/bash
# Script description
# Usage: ./script.sh [options]
# Configuration
set -euo pipefail
# Functions
function_name() {
# Function body
}
# Main
main() {
# Script logic
}
main "$@"
That's it. That's the structure.
The Shebang
#!/bin/bash
What it does: Tells Linux which interpreter to use.
My take: Always include it. Always. Without it, scripts might not run.
set -euo pipefail
set -euo pipefail
What it does:
-e= Exit on error-u= Exit on undefined variable-o pipefail= Fail if any pipe command fails
My take: Use this. Always. It catches errors early. Makes scripts more reliable.
Script Directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename "$0")"
What it does: Gets the directory where the script is located.
My take: Useful for relative paths. Use it when scripts reference other files.
Configuration Section
# Configuration
SOURCE_DIR="${SOURCE_DIR:-/default/path}"
BACKUP_DIR="${BACKUP_DIR:-/backup}"
MAX_BACKUPS=7
The ${VAR:-default}: Uses VAR if set, otherwise uses default.
My take: Put configuration at the top. Makes it easy to modify.
Functions Section
# Functions
log() {
local level="$1"
shift
local message="$*"
echo "[$(date)] [$level] $message"
}
backup_file() {
local file="$1"
cp "$file" "${file}.backup"
}
My take: Put functions before main. Organizes code. Makes it readable.
Main Function
main() {
# Script logic here
validate_input
process_files
cleanup
}
main "$@"
The "$@": Passes all arguments to main.
My take: Put main logic in a function. Makes scripts testable. Makes structure clear.
Comments
# This is a comment
# Explain WHY, not WHAT
My take: Comment why, not what. Code should be self-explanatory. Comments explain decisions.
Error Handling
error_exit() {
echo "Error: $1" >&2
exit 1
}
if [ ! -f "$file" ]; then
error_exit "File not found: $file"
fi
My take: Handle errors. Don't let scripts fail silently. Tell users what went wrong.
Logging
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}
log "INFO" "Script started"
log "ERROR" "Something went wrong"
My take: Log important events. Makes debugging easier. Makes scripts professional.
Complete Example
#!/bin/bash
# Backup script
# Usage: ./backup.sh [source] [destination]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="${1:-/home/ubuntu/data}"
BACKUP_DIR="${2:-/backup}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
backup() {
local source="$1"
local dest="$2"
if [ ! -d "$source" ]; then
echo "Error: Source directory not found: $source"
exit 1
fi
log "Backing up $source to $dest"
cp -r "$source" "$dest"
log "Backup complete"
}
main() {
log "Script started"
backup "$SOURCE_DIR" "$BACKUP_DIR"
log "Script completed"
}
main "$@"
My take: This is a good structure. Follow it. Your scripts will be better.
Common Mistakes (I've Made These)
-
Forgetting shebang: Scripts might not run. Always include
#!/bin/bash. -
Not using
set -euo pipefail: Scripts continue after errors. Use it. -
No error handling: Scripts fail silently. Handle errors. Tell users what went wrong.
-
No logging: Hard to debug. Add logging. Makes life easier.
-
Everything in main: No functions. Hard to read. Organize with functions.
What's Next?
Now that you understand script structure, you can write professional scripts. Or learn about Variables to store data.
Personal note: When I started, my scripts had no structure. Everything was in one big block. Then I learned structure. Now my scripts are organized. Readable. Maintainable. Structure matters.