Advanced Bash Scripting
Advanced bash scripting builds on fundamentals to create production-quality scripts that handle errors gracefully, clean up after themselves, process large files efficiently, and are safe to run in automated environments. The difference between a beginner script and a production script is mostly in error handling: what happens when a command fails, what state is left behind, and how much information is logged for debugging.
Error handling and traps
nano /usr/local/bin/safe-deploy.sh
safe-deploy.sh — trap-based cleanup
#!/bin/bash
set -euo pipefail
TEMP_DIR=$(mktemp -d) # Create unique temp directory
LOCK_FILE="/var/run/deploy.lock"
# Cleanup function — runs on ANY exit (success or failure):
cleanup() {
local exit_code=$?
rm -rf "${TEMP_DIR}"
rm -f "${LOCK_FILE}"
if [[ $exit_code -ne 0 ]]; then
echo "FAILED: deploy script exited with code ${exit_code}" >&2
fi
}
# Register cleanup to run on EXIT, INT (Ctrl+C), TERM:
trap cleanup EXIT INT TERM
# Prevent concurrent runs:
if [[ -f "${LOCK_FILE}" ]]; then
echo "Another deploy is running (lock: ${LOCK_FILE})" >&2
exit 1
fi
touch "${LOCK_FILE}"
echo "Deploying to ${TEMP_DIR}..."
# ... deployment commands here ...
echo "Deploy complete"
Arrays and string manipulation
# Bash arrays:
SERVERS=("web-01" "web-02" "db-01")
# Loop over array:
for server in "${SERVERS[@]}"; do
echo "Checking ${server}..."
ssh "${server}" uptime
done
# Array length:
echo "Server count: ${#SERVERS[@]}"
# String manipulation:
FILENAME="backup-2025-06-09.tar.gz"
echo "${FILENAME%%.*}" # backup-2025-06-09 (remove longest suffix match .*)
echo "${FILENAME##*.}" # gz (remove longest prefix match *.)
echo "${FILENAME/backup/archive}" # archive-2025-06-09.tar.gz (replace)
DATE_STR="2025-06-09"
YEAR="${DATE_STR:0:4}" # 2025 (substring: start, length)
MONTH="${DATE_STR:5:2}" # 06
# Parse CSV output:
while IFS=',' read -r name size status; do
echo "Name: ${name}, Size: ${size}, Status: ${status}"
done < servers.csv
File and log processing
nano /usr/local/bin/log-summary.sh
log-summary.sh — processing logs efficiently
#!/bin/bash
set -euo pipefail
LOG_FILE="${1:-/var/log/nginx/access.log}"
OUTPUT_FILE="/tmp/log-summary-$(date +%Y%m%d).txt"
# Check file exists and is readable:
if [[ ! -r "${LOG_FILE}" ]]; then
echo "Cannot read log file: ${LOG_FILE}" >&2
exit 1
fi
echo "Analyzing: ${LOG_FILE}" > "${OUTPUT_FILE}"
echo "Generated: $(date)" >> "${OUTPUT_FILE}"
echo "" >> "${OUTPUT_FILE}"
# Top 10 IP addresses:
echo "=== Top 10 Client IPs ===" >> "${OUTPUT_FILE}"
awk '{print $1}' "${LOG_FILE}" | sort | uniq -c | sort -rn | head -10 >> "${OUTPUT_FILE}"
# HTTP status code summary:
echo "=== Status Codes ===" >> "${OUTPUT_FILE}"
awk '{print $9}' "${LOG_FILE}" | sort | uniq -c | sort -rn >> "${OUTPUT_FILE}"
# 404 errors:
echo "=== 404 Not Found URLs ===" >> "${OUTPUT_FILE}"
awk '$9 == 404 {print $7}' "${LOG_FILE}" | sort | uniq -c | sort -rn | head -20 >> "${OUTPUT_FILE}"
cat "${OUTPUT_FILE}"
Script best practices
Production script checklist:
set -euo pipefail → fail fast on errors
trap cleanup EXIT → always clean up temp files and locks
[[ $# -ne N ]] → validate argument count
[[ -f "${file}" ]] → validate inputs before using them
local variables → scope variables inside functions
Log with timestamps → echo "[$(date)] ..." >> "${LOG_FILE}"
Redirect errors → echo "Error" >&2 (not stdout)
Lock files → prevent concurrent execution
Dry-run mode → add --dry-run flag that echoes commands instead of running
# Dry-run pattern (safe to test):
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
run_cmd() {
if $DRY_RUN; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
# Usage:
run_cmd rm -rf /old/directory
run_cmd systemctl restart nginx
Conclusion
The trap cleanup EXIT pattern is the single most important advanced technique for production scripts: it ensures temp files and lock files are always removed regardless of how the script exits — success, error, or Ctrl+C. Without it, failed scripts leave lock files that block future runs and temp files that fill disk. Combine this with lock files (/var/run/scriptname.lock) to prevent cron jobs from running concurrently when a previous instance is still running.
Need help with Ubuntu administration?
Work directly with Muhammad Irfan Aslam for Ubuntu Server, Linux, cloud, Docker, DevOps, CI/CD, or infrastructure troubleshooting support.
Work with Muhammad Irfan Aslam