Skip to content

Bash Scripting Cheat Sheet

Remember: Every production bash script starts with set -euo pipefail. Mnemonic: "EUP" — Exit on error, Undefined variable check, Pipe failure propagation. Without these, scripts silently continue after errors, use empty strings for unset variables, and hide failures in pipelines. These three flags prevent the most common scripting bugs.

Script Header

#!/usr/bin/env bash
set -euo pipefail    # Exit on error, undefined vars, pipe failures
Flag Meaning
-e Exit on first error
-u Error on undefined variables
-o pipefail Pipe fails if any command fails
-x Debug: print each command before running

Variables

name="world"
echo "Hello $name"              # Double quotes: expand variables
echo 'Hello $name'              # Single quotes: literal string
echo "Path: ${HOME}/bin"        # Braces for clarity

# Default values
echo "${VAR:-default}"          # Use default if unset
echo "${VAR:=default}"          # Set AND use default if unset
echo "${VAR:?error msg}"       # Error if unset

# String operations
echo "${name^^}"                # WORLD (uppercase)
echo "${name,,}"                # world (lowercase)
echo "${name:0:3}"              # wor (substring)
echo "${filename%.txt}"         # Remove .txt suffix
echo "${path##*/}"              # Basename (remove longest prefix)
echo "${path%/*}"               # Dirname (remove shortest suffix)

Conditionals

if [[ -f "$file" ]]; then       # File exists
  echo "found"
elif [[ -d "$dir" ]]; then      # Directory exists
  echo "is dir"
else
  echo "not found"
fi

# Test operators
[[ -f file ]]       # File exists
[[ -d dir ]]        # Directory exists
[[ -z "$var" ]]     # String is empty
[[ -n "$var" ]]     # String is not empty
[[ "$a" == "$b" ]]  # String equality
[[ "$a" =~ regex ]] # Regex match
[[ $n -gt 5 ]]      # Numeric greater than (-lt, -eq, -ne, -ge, -le)

# One-liners
[[ -f config.yaml ]] && echo "exists" || echo "missing"

Loops

# For loop
for f in *.yaml; do
  echo "Processing $f"
done

# C-style for
for ((i=0; i<10; i++)); do
  echo "$i"
done

# While loop
while read -r line; do
  echo "$line"
done < file.txt

# While with command
kubectl get pods -w | while read -r line; do
  echo "$(date): $line"
done

# Process substitution
while read -r pod status; do
  echo "$pod is $status"
done < <(kubectl get pods --no-headers -o custom-columns=NAME:.metadata.name,STATUS:.status.phase)

Functions

deploy() {
  local env="$1"           # Local variable
  local version="${2:-latest}"  # With default

  if [[ -z "$env" ]]; then
    echo "Usage: deploy <env> [version]" >&2
    return 1
  fi

  echo "Deploying $version to $env"
}

deploy prod v2.1.0

Arrays

# Indexed array
files=("a.txt" "b.txt" "c.txt")
echo "${files[0]}"              # First element
echo "${files[@]}"              # All elements
echo "${#files[@]}"             # Length

for f in "${files[@]}"; do      # Iterate (quote to handle spaces)
  echo "$f"
done

# Associative array
declare -A config
config[host]="localhost"
config[port]="8080"
echo "${config[host]}:${config[port]}"

I/O Redirection

cmd > file        # Stdout to file (overwrite)
cmd >> file       # Stdout to file (append)
cmd 2> file       # Stderr to file
cmd &> file       # Both stdout+stderr to file
cmd 2>&1          # Stderr to stdout
cmd < file        # Stdin from file
cmd1 | cmd2       # Pipe stdout

# Here document
cat <<EOF
Hello $name
EOF

# Here string
grep "error" <<< "$log_output"

Text Processing

# grep
grep -r "pattern" .             # Recursive search
grep -i "error" log             # Case insensitive
grep -c "error" log             # Count matches
grep -v "debug" log             # Invert (exclude)
grep -A3 "error" log            # 3 lines after match
grep -E "err|warn" log          # Extended regex (OR)

# sed
sed 's/old/new/' file           # Replace first occurrence
sed 's/old/new/g' file          # Replace all
sed -i 's/old/new/g' file      # In-place edit
sed -n '10,20p' file            # Print lines 10-20
sed '/pattern/d' file           # Delete matching lines

# awk
awk '{print $1, $3}' file      # Print columns 1 and 3
awk -F: '{print $1}' /etc/passwd  # Custom delimiter
awk '$3 > 100' file             # Filter by condition
awk '{sum+=$1} END {print sum}' # Sum a column

# sort + uniq
sort file | uniq -c | sort -rn  # Count unique, sorted by frequency

# cut
cut -d: -f1 /etc/passwd        # First field, colon-delimited
cut -c1-10 file                 # First 10 characters

# xargs
find . -name "*.log" | xargs rm
echo "a b c" | xargs -n1       # One arg per line
find . -name "*.yaml" | xargs -I{} kubectl apply -f {}

Error Handling

# Trap for cleanup
cleanup() {
  rm -f "$tmpfile"
}
trap cleanup EXIT               # Run on script exit

# Trap for errors
trap 'echo "Error at line $LINENO" >&2' ERR

# Try/catch pattern
if ! output=$(some_command 2>&1); then
  echo "Failed: $output" >&2
  exit 1
fi

Argument Parsing

# Simple positional
script_name="$0"
first_arg="$1"
all_args="$@"
arg_count="$#"

# getopts
while getopts "e:v:dh" opt; do
  case $opt in
    e) env="$OPTARG" ;;
    v) version="$OPTARG" ;;
    d) debug=true ;;
    h) usage; exit 0 ;;
    *) usage; exit 1 ;;
  esac
done
shift $((OPTIND - 1))   # Remaining args in $@

Common DevOps Patterns

# Retry with backoff
retry() {
  local max=3 delay=2
  for ((i=1; i<=max; i++)); do
    "$@" && return 0
    echo "Attempt $i failed, retrying in ${delay}s..." >&2
    sleep "$delay"
    delay=$((delay * 2))
  done
  return 1
}
retry curl -sf http://localhost:8080/health

# Wait for service
wait_for() {
  local host="$1" port="$2" timeout="${3:-30}"
  for ((i=0; i<timeout; i++)); do
    nc -z "$host" "$port" 2>/dev/null && return 0
    sleep 1
  done
  echo "Timeout waiting for $host:$port" >&2
  return 1
}
wait_for localhost 5432 60

# Safe temp file
tmpfile=$(mktemp)
trap "rm -f $tmpfile" EXIT