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¶
| 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