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)