Advanced Bash Scripting

Master advanced Bash techniques: error handling, debugging, best practices, set options, getopts, heredocs, and writing production-quality scripts.

📖 5 min read📅 2026-02-10Advanced Topics

Strict Mode

Always start scripts with strict mode:

#!/bin/bash
set -euo pipefail
 
# What each option does:
# set -e          Exit on any error
# set -u          Error on undefined variables
# set -o pipefail Exit on pipe failures (not just last command)

Understanding set Options

# set -e: Exit on error
set -e
false          # Script exits here
echo "Never reached"
 
# set -u: Undefined variables are errors
set -u
echo $undefined_var    # Error!
 
# set -o pipefail: Pipe failures
set -o pipefail
false | true           # Exits with error (false failed)
 
# set -x: Debug mode (print each command)
set -x
echo "This will be traced"
set +x   # Turn off tracing

Error Handling

Exit Codes

# Every command returns an exit code
# 0 = success, non-zero = failure
 
ls /tmp
echo $?    # 0 (success)
 
ls /nonexistent
echo $?    # 2 (error)
 
# Set custom exit codes
exit 0     # Success
exit 1     # General error
exit 2     # Misuse of command

Handling Errors

#!/bin/bash
set -euo pipefail
 
# Method 1: || operator (OR)
mkdir /some/dir || echo "Failed to create directory"
 
# Method 2: if statement
if ! mkdir /some/dir 2>/dev/null; then
    echo "ERROR: Could not create directory" >&2
    exit 1
fi
 
# Method 3: trap ERR
trap 'echo "Error on line $LINENO. Exit code: $?" >&2' ERR
 
# Method 4: Custom error function
die() {
    echo "ERROR: $*" >&2
    exit 1
}
 
[[ -f "config.txt" ]] || die "config.txt not found"

Comprehensive Error Handling

#!/bin/bash
set -euo pipefail
 
# Error handler
error_handler() {
    local line=$1
    local exit_code=$2
    local command=$3
    echo "ERROR: Command '$command' failed at line $line with exit code $exit_code" >&2
}
trap 'error_handler $LINENO $? "$BASH_COMMAND"' ERR
 
# Cleanup handler
cleanup() {
    echo "Cleaning up..."
    rm -f "$TEMP_FILE"
}
trap cleanup EXIT
 
TEMP_FILE=$(mktemp)
echo "Using temp file: $TEMP_FILE"
 
# Your script logic here...

Debugging

# Run with debug output
bash -x script.sh
 
# Debug specific sections
set -x
# ... code to debug ...
set +x
 
# Custom debug function
DEBUG=${DEBUG:-false}
debug() {
    if [[ "$DEBUG" == "true" ]]; then
        echo "[DEBUG] $*" >&2
    fi
}
 
debug "Processing file: $filename"
 
# Run with debugging
DEBUG=true ./script.sh

Argument Parsing with getopts

#!/bin/bash
# deploy.sh - Deploy application
 
set -euo pipefail
 
# Defaults
ENVIRONMENT="staging"
VERBOSE=false
DRY_RUN=false
 
usage() {
    cat << EOF
Usage: $(basename "$0") [OPTIONS] <app-name>
 
Options:
  -e ENV    Environment (staging|production) [default: staging]
  -v        Verbose output
  -n        Dry run (don't actually deploy)
  -h        Show this help message
 
Examples:
  $(basename "$0") -e production myapp
  $(basename "$0") -vn -e staging myapp
EOF
    exit 1
}
 
while getopts ":e:vnh" opt; do
    case $opt in
        e) ENVIRONMENT="$OPTARG" ;;
        v) VERBOSE=true ;;
        n) DRY_RUN=true ;;
        h) usage ;;
        :) echo "Option -$OPTARG requires an argument" >&2; usage ;;
        \?) echo "Invalid option: -$OPTARG" >&2; usage ;;
    esac
done
shift $((OPTIND - 1))
 
# Remaining arguments
APP_NAME="${1:?App name is required. Use -h for help.}"
 
echo "Deploying $APP_NAME to $ENVIRONMENT"
$VERBOSE && echo "Verbose mode enabled"
$DRY_RUN && echo "DRY RUN - no changes will be made"

Here Documents and Here Strings

# Here document
cat << EOF
Hello, $USER!
Today is $(date +%A)
Your home directory is $HOME
EOF
 
# Indented here document (<<- strips leading tabs)
if true; then
    cat <<- EOF
	This is indented with tabs
	And tabs are stripped
	EOF
fi
 
# Quoted here document (no variable expansion)
cat << 'EOF'
This $variable will not expand
Neither will $(this command)
EOF
 
# Here string
grep "error" <<< "This line has an error in it"
 
# Pipe here document
cat << EOF | grep "important"
line 1
important line
line 3
EOF

Best Practices

1. Always Quote Variables

# BAD: word splitting and globbing
file=$1
rm $file              # Breaks if file has spaces
 
# GOOD: quoted
file="$1"
rm "$file"

2. Use Functions for Organization

#!/bin/bash
set -euo pipefail
 
# Configuration
readonly LOG_DIR="/var/log/myapp"
readonly MAX_RETRIES=3
 
# Functions
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"; }
error() { log "ERROR: $*" >&2; }
info() { log "INFO: $*"; }
 
validate_input() {
    [[ -n "${1:-}" ]] || { error "Input required"; return 1; }
}
 
process_data() {
    local input="$1"
    info "Processing: $input"
    # ... processing logic ...
}
 
# Main
main() {
    validate_input "${1:-}"
    process_data "$1"
    info "Complete!"
}
 
main "$@"

3. Template for Production Scripts

#!/usr/bin/env bash
#
# Script: deploy.sh
# Description: Deploy application to servers
# Author: Your Name
# Date: 2026-02-10
# Version: 1.0.0
 
set -euo pipefail
IFS=$'\n\t'
 
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
 
# Logging
log() { printf '[%s] %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*"; }
error() { log "ERROR: $*" >&2; }
fatal() { error "$@"; exit 1; }
info() { log "INFO: $*"; }
debug() { [[ "${VERBOSE:-false}" == true ]] && log "DEBUG: $*"; }
 
# Cleanup
cleanup() {
    local exit_code=$?
    # Cleanup code here
    exit $exit_code
}
trap cleanup EXIT
 
# Argument parsing
usage() {
    cat << EOF
Usage: $SCRIPT_NAME [options]
  -h  Show help
  -v  Verbose mode
EOF
}
 
# Main function
main() {
    info "Starting $SCRIPT_NAME"
    # Your logic here
    info "$SCRIPT_NAME completed successfully"
}
 
# Entry point
main "$@"

Exercises

  1. Add comprehensive error handling to an existing script
  2. Write a script with getopts that manages user accounts (add, delete, list)
  3. Create a deployment script that includes dry-run mode, logging, and rollback
  4. Write a script that handles signals (SIGINT, SIGTERM) gracefully
  5. Build a reusable Bash script template with logging, error handling, and argument parsing

Congratulations! You've completed the Bash tutorial series. You now have a strong foundation in Bash scripting and Linux command line tools!