Bash Scripting Patterns
Script Header (always use)
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
Error Handling with trap
cleanup() {
local exit_code=$?
echo "[${SCRIPT_NAME}] Cleaning up (exit: ${exit_code})" >&2
rm -f /tmp/myapp-lock
exit "${exit_code}"
}
trap cleanup EXIT INT TERM
die() {
echo "[ERROR] $*" >&2
exit 1
}
[[ -f config.yaml ]] || die "config.yaml not found"
Argument Parsing
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} [OPTIONS]
Options:
-e, --env ENV Environment (dev|staging|prod) [required]
-t, --tag TAG Docker image tag [default: latest]
-d, --dry-run Print commands, don't execute
-h, --help Show this help
EOF
}
ENV=""
TAG="latest"
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env) ENV="$2"; shift 2 ;;
-t|--tag) TAG="$2"; shift 2 ;;
-d|--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unknown option: $1" ;;
esac
done
[[ -n "${ENV}" ]] || die "--env is required"
[[ "${ENV}" =~ ^(dev|staging|prod)$ ]] || die "Invalid env: ${ENV}"
Run or Dry-Run Helper
run() {
echo "+ $*" >&2
if [[ "${DRY_RUN}" == "false" ]]; then
"$@"
fi
}
run kubectl apply -f deployment.yaml
run helm upgrade myapp ./chart --atomic
Retry with Backoff
retry() {
local max_attempts=$1; shift
local delay=5
local attempt=1
until "$@"; do
if (( attempt >= max_attempts )); then
echo "[ERROR] Command failed after ${max_attempts} attempts: $*" >&2
return 1
fi
echo "[WARN] Attempt ${attempt}/${max_attempts} failed. Retrying in ${delay}s..." >&2
sleep "${delay}"
(( attempt++ ))
(( delay = delay * 2 ))
done
}
retry 5 curl -sf https://api.example.com/health
Parallel Execution
# Run jobs in parallel, wait for all, capture failures
pids=()
for region in us-east-1 eu-west-1 ap-southeast-1; do
./deploy.sh --region "${region}" &
pids+=($!)
done
failed=0
for pid in "${pids[@]}"; do
if ! wait "${pid}"; then
echo "[ERROR] Job ${pid} failed" >&2
(( failed++ ))
fi
done
(( failed == 0 )) || die "${failed} deployment(s) failed"
Heredoc for Config
cat > /etc/myapp/config.yaml <<EOF
environment: ${ENV}
database:
host: ${DB_HOST:-localhost}
port: ${DB_PORT:-5432}
name: myapp_${ENV}
log_level: ${LOG_LEVEL:-info}
EOF
Logging Functions
info() { echo "[$(date -u +%T)] [INFO] $*"; }
warn() { echo "[$(date -u +%T)] [WARN] $*" >&2; }
error() { echo "[$(date -u +%T)] [ERROR] $*" >&2; }
info "Starting deployment to ${ENV}"
warn "This will restart the service"
Key Rules
set -euo pipefail on every script — e exits on error, u catches unset vars, o pipefail catches pipe failures
- Quote every variable:
"${VAR}" not $VAR — prevents word splitting on spaces
- Use
[[ ]] not [ ] — safer string comparisons, no word splitting
- Prefer
printf over echo for portable output
- ShellCheck every script:
shellcheck myscript.sh