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 tracingError 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 commandHandling 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.shArgument 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
EOFBest 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
- Add comprehensive error handling to an existing script
- Write a script with
getoptsthat manages user accounts (add, delete, list) - Create a deployment script that includes dry-run mode, logging, and rollback
- Write a script that handles signals (SIGINT, SIGTERM) gracefully
- 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!