fundamentals
Bash Scripting for DevOps: Patterns That Survive Production
Beginner
12 minutes
October 2026
CloudOpsGuide Team
Bash Scripting for DevOps: Patterns That Survive Production
Bash is the glue language of DevOps — glue scripts, cron jobs, CI steps, container entrypoints. You don't need to be a shell wizard; you need reliable patterns and an allergy to sharp edges. Here's the working set.
Table of Contents
- The prologue every script needs
- Variables, arguments, quoting
- Conditionals and loops
- Command substitution and checks
- Functions and error handling
- The useful one-liners
- Tools that make you better
The prologue every script needs
#!/usr/bin/env bash
set -euo pipefail
- -e — exit on first error. Without it, failures silently cascade.
- -u — error on unset variables instead of expanding to empty string.
- pipefail — a failing command inside a pipe fails the pipeline (not just the last command).
Skip this line and debugging becomes archaeology.
Variables, arguments, quoting
ENV="${1:-dev}" # arg 1 or default
IMAGE_TAG="${GIT_SHA:-latest}"
readonly CONFIG_DIR="/etc/myapp" # immutable
# quote everything, always:
cp "$src" "$dest" # handles spaces safely
# ${var:-default} — fallback if unset
# ${var:?error} — hard fail if unset (great for required config)
The unquoted $var splitting on whitespace has caused more production incidents than any other shell feature. Quote it.
Conditionals and loops
if [[ -f /etc/app.conf ]]; then
echo "config found"
elif [[ "$ENV" == "prod" ]]; then
echo "extra care"
fi
for host in web1 web2 web3; do
ssh "$host" 'systemctl restart app'
done
while IFS= read -r line; do
echo "$line"
done < hosts.txt
Use [[ ]] (not [ ]) — it handles strings, patterns, and && safely.
Command substitution and checks
today=$(date +%F)
count=$(kubectl get pods --no-headers | wc -l)
if command -v docker >/dev/null; then
echo "docker installed"
fi
# exit codes
if curl -fsS http://localhost:8080/health; then
echo "healthy"
else
echo "down" >&2; exit 1
fi
Functions and error handling
log() { echo "[$(date +%T)] $*" ; }
die() { log "ERROR: $*" >&2; exit 1; }
deploy() {
local env="$1" # always declare locals
[[ -n "$env" ]] || die "env required"
log "deploying to $env"
}
# trap cleanup on any exit
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
The useful one-liners
ps aux | awk '$3 > 50 {print $2, $11}' # cpu hogs
find /var/log -name "*.log" -mtime +30 -delete
grep -rl "TODO" src/ | wc -l
tar czf backup-$(date +%F).tar.gz /etc/app
Tools that make you better
- shellcheck — lint your scripts; catches the footguns (shellcheck script.sh, or in CI).
- shfmt — consistent formatting.
- set -x — trace mode; prints every command before running. Invaluable for debugging CI.
Rule of thumb: when a script grows past ~100 lines, needs real data structures, or has error paths you can't express cleanly — reach for Python or Go. Bash is glue, not an application platform.
Related Articles
- Essential Linux Commands Every DevOps Engineer Needs
- AWS for DevOps Engineers: The Services That Actually Matter
Last Updated: October 2026 Author: CloudOpsGuide Team Difficulty: Beginner Estimated Reading Time: 12 minutes