This template provides a robust starting point for any Bash script, incorporating best practices for error handling, variable management, and script structure.

#!/bin/bash

# Strict mode:
# -e: Exit immediately if a command exits with a non-zero status.
# -u: Treat unset variables as an error and exit immediately.
# -o pipefail: The return value of a pipeline is the status of the last command
#              to exit with a non-zero status, or zero if no command exited
#              with a non-zero status.
set -euo pipefail

# --- Configuration ---
# Define any script-specific variables here.
# Example:
# LOG_FILE="/var/log/my_script.log"
# TARGET_DIR="/opt/my_app/data"

# --- Functions ---
# Define reusable functions here.

# Example: Logging function
log_message() {
  local message="$1"
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
  echo "[$timestamp] $message" # >> "$LOG_FILE" # Uncomment to log to file
}

# Example: Function to check command success
check_command() {
  if [ $? -ne 0 ]; then
    log_message "ERROR: Command failed: $1"
    exit 1
  fi
}

# --- Main Execution ---
log_message "Script started."

# --- Script Logic ---
# Place your main script logic here.
# Use functions for modularity and readability.

# Example: Creating a directory
# mkdir -p "$TARGET_DIR"
# check_command "mkdir $TARGET_DIR"
# log_message "Ensured directory '$TARGET_DIR' exists."

# Example: Copying a file
# cp /path/to/source/file.txt "$TARGET_DIR/"
# check_command "cp /path/to/source/file.txt $TARGET_DIR/"
# log_message "Copied file to '$TARGET_DIR'."

# Example: Processing data
# grep "pattern" /path/to/data.log | while read -r line; do
#   log_message "Found: $line"
# done
# check_command "grep and while loop" # Note: pipefail handles the grep part.

log_message "Script finished successfully."

exit 0

The set -euo pipefail directive is the cornerstone of robust Bash scripting. set -e ensures that your script will exit immediately if any command fails, preventing cascading errors. set -u stops your script from proceeding if you accidentally use an undefined variable, saving you from subtle bugs. set -o pipefail is crucial for pipelines; it guarantees that a failure in any part of a pipeline will cause the entire pipeline to be considered a failure, which is often the desired behavior.

Inside the "Configuration" section, you’ll define all your script’s variables. This centralizes your settings, making them easy to find, modify, and understand. Use descriptive variable names. For instance, instead of d=/var/log, use LOG_FILE=/var/log/my_script.log. This clarity is invaluable, especially when revisiting scripts later or when others need to understand them.

The "Functions" section is where you encapsulate reusable blocks of code. This promotes modularity, making your script easier to read, test, and maintain. A good practice is to create specific functions for tasks like logging, error checking, or performing complex operations. This keeps the main execution flow clean and focused.

The log_message function is a simple yet powerful tool. It prefixes each message with a timestamp, providing a clear audit trail of your script’s execution. Uncommenting the >> "$LOG_FILE" line allows you to redirect these messages to a file for persistent logging, which is essential for debugging and monitoring.

The check_command function demonstrates how to integrate error checking directly into your script’s logic. After any command that might fail, you check the exit status ($?). If it’s non-zero, an error is logged, and the script exits. This prevents the script from continuing with invalid data or an incomplete operation.

The "Main Execution" block is the entry point. It typically starts with a log message indicating the script has begun. The "Script Logic" section is where the core functionality resides. Here, you’ll call your defined functions and execute the commands necessary to achieve your script’s purpose. Breaking down complex tasks into smaller, manageable steps within functions significantly improves readability and debugging.

When you’re copying and pasting this template, remember to fill in the "Configuration" section with your specific paths, filenames, and parameters. Then, populate the "Script Logic" section with your commands, leveraging the provided functions for logging and error handling.

One subtle but critical aspect of set -euo pipefail is how it interacts with conditional statements and loops. For example, if you have an if statement that checks if a file exists: if [ -f "$NON_EXISTENT_FILE" ]; then ... fi, and NON_EXISTENT_FILE is not set (due to set -u), the script will exit before the if condition is even evaluated. Similarly, in a pipeline like command1 | command2, if command2 fails but command1 succeeds, pipefail ensures the entire pipeline is treated as a failure.

The next logical step after mastering this template is to explore more advanced Bash features like signal handling (trap), argument parsing (getopts), and creating more sophisticated logging mechanisms.

Want structured learning?

Take the full Bash course →