Bash Scripting Guide

Script Header & Error Handling

#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # set -e : exit on any error # set -u : error on unset variables # set -o pipefail : pipeline fails if any command fails # IFS : safer word splitting # Trap for cleanup cleanup() { echo "Cleaning up..." rm -f /tmp/myapp.lock } trap cleanup EXIT trap 'echo "Error at line $LINENO"; exit 1' ERR # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Logging functions log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*"; } warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*" >&2; } err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }

Variables

# Variable assignment (no spaces around =) name="Alice" count=42 readonly PI=3.14159 # Default values app_name="${APP_NAME:-myapp}" # use default if unset app_dir="${APP_DIR:=/opt/myapp}" # set and use default if unset db_host="${DB_HOST:?'DB_HOST is required'}" # error if unset # Variable expansion echo "${name}" # basic echo "${name^^}" # uppercase echo "${name,,}" # lowercase echo "${name:0:3}" # substring (pos, len) echo "${name/Alice/Bob}" # replace first echo "${name//l/L}" # replace all # Arithmetic x=$((10 + 5)) y=$(( x * 2 )) let "z = x + y" echo $(( 2**10 )) # 1024 # String length echo "${#name}" # 5 # Command substitution today=$(date +%Y%m%d) files=$(ls -1 *.sh 2>/dev/null | wc -l)

Conditionals

# if / elif / else if [[ -f /etc/nginx/nginx.conf ]]; then echo "nginx is installed" elif [[ -f /etc/apache2/apache2.conf ]]; then echo "apache is installed" else echo "no web server found" fi # Common test operators # [[ -f file ]] file exists and is regular file # [[ -d dir ]] directory exists # [[ -z "$var" ]] string is empty # [[ -n "$var" ]] string is non-empty # [[ "$a" == "$b" ]] string equality # [[ "$a" != "$b" ]] string inequality # [[ $x -gt 5 ]] integer greater than # [[ $x -le 10 ]] integer less than or equal # Logical operators in [[ if [[ -n "$name" && $count -gt 0 ]]; then echo "name is set and count is positive" fi # Case statement case "$1" in start) systemctl start myapp ;; stop) systemctl stop myapp ;; restart) systemctl restart myapp ;; *) echo "Usage: $0 {start|stop|restart}"; exit 1 ;; esac

Loops

# for loop over list for server in web1 web2 web3; do echo "Deploying to $server" ssh "$server" "sudo systemctl restart myapp" done # for loop with range for i in {1..10}; do echo "Item $i" done # for loop with C-style for (( i=0; i<5; i++ )); do echo "Index: $i" done # for loop over files for file in *.log; do [[ -f "$file" ]] || continue gzip "$file" done # while loop while IFS= read -r line; do echo "Processing: $line" done < /etc/hosts # while with counter count=0 while [[ $count -lt 5 ]]; do echo "Count: $count" (( count++ )) done # until loop until curl -sf http://localhost:8080/health; do echo "Waiting for service..." sleep 2 done

Functions

# Function definition deploy_app() { local app_name="$1" local version="${2:-latest}" local env="${3:-dev}" log "Deploying ${app_name}:${version} to ${env}" if [[ -z "$app_name" ]]; then err "app_name is required" return 1 fi # ... deployment logic return 0 # explicit success } # Call function deploy_app "myapp" "v2.1.0" "production" # Capture return value if deploy_app "myapp" "v2.1.0" "production"; then log "Deployment successful" else err "Deployment failed" exit 1 fi # Function returning value via echo get_latest_tag() { git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0" } latest=$(get_latest_tag)