Shell Scripting Deep Dive Cheat Sheet
Advanced Bash scripting patterns including parameter expansion, error handling, arrays, and safe scripting practices.
Strict Mode & Error Handling
Make scripts fail fast and loudly.
#!/usr/bin/env bashset -euo pipefailIFS=$'\n\t'# -e: exit on any command failure# -u: error on unset variables# -o pipefail: fail if any command in a pipeline failstrap 'echo "Error on line $LINENO" >&2' ERR
Parameter Expansion
Common Bash string/variable manipulations.
name=${1:-default} # Default value if $1 unset/emptyfile=${path##*/} # Strip longest match from front (basename)dir=${path%/*} # Strip shortest match from back (dirname)upper=${str^^} # Uppercase entire stringlen=${#str} # String lengthsub=${str:0:5} # Substring (offset, length)replaced=${str//foo/bar} # Replace all occurrences
Arrays & Loops
Working with indexed arrays and iteration.
arr=(one two three)for item in "${arr[@]}"; do echo "$item"doneecho "Count: ${#arr[@]}"# Read a file line by line safelywhile IFS= read -r line; do echo "$line"done < input.txt
Common Gotchas
Mistakes that cause subtle bugs.
- Unquoted variables- Always quote "$var" to prevent word splitting and glob expansion
- [ vs [[- Prefer [[ ]] in Bash for safer comparisons and pattern matching support
- $(cmd) vs `cmd`- Prefer $() for command substitution; it nests cleanly and is more readable
- local in functions- Declare function variables with 'local' to avoid leaking into global scope
- set -e pitfalls- set -e does not trigger inside conditionals like 'if cmd; then' — errors there must be checked explicitly
- shellcheck- Static analysis tool that catches quoting, portability, and logic bugs before runtime
Traps & Guaranteed Cleanup
Run cleanup code reliably on exit, error, or interrupt, even from mid-script failures.
#!/usr/bin/env bashset -euo pipefailtmpdir=$(mktemp -d)cleanup() { local exit_code=$? rm -rf -- "$tmpdir" trap - EXIT # avoid double-firing exit "$exit_code"}trap cleanup EXIT INT TERM# ERR trap fires on any failing command (with set -e active)trap 'echo "Failed at line $LINENO: $BASH_COMMAND" >&2' ERR# Work with $tmpdir here; cleanup runs no matter how the script exitscurl -fsSL https://example.com/data.tar.gz -o "$tmpdir/data.tar.gz"
Process Substitution & Named Pipes
Feed command output as a file, or diff/compare streams without temp files.
# Treat command output as a readable filediff <(sort file1.txt) <(sort file2.txt)# Feed a command's stdin from another command's outputwhile read -r line; do echo "got: $line"; done < <(grep ERROR app.log)# Write to two consumers at oncetee >(gzip > out.gz) >(sha256sum > out.sha256) > /dev/null < input.txt# Named pipe (FIFO) for producer/consumer across processesmkfifo /tmp/pipeproducer > /tmp/pipe &consumer < /tmp/pipe
Associative Arrays & Namerefs
Use Bash 4+ hash maps and indirect variable references for building small config/lookup tables.
declare -A config=( [host]="localhost" [port]="5432" [user]="admin")for key in "${!config[@]}"; do echo "$key = ${config[$key]}"done[[ -v config[host] ]] && echo "host is set"# nameref: pass a variable by reference into a functionappend_result() { local -n target=$1 # target is now an alias for the caller's variable target+=("$2")}results=()append_result results "first"append_result results "second"echo "${results[@]}"
Robust Argument Parsing with getopts
Handle short flags, combined flags, and required option arguments the POSIX way.
#!/usr/bin/env bashset -euo pipefailusage() { echo "Usage: $0 [-v] [-o output] -i input" >&2; exit 1; }verbose=0output="-"input=""while getopts ":vo:i:" opt; do case "$opt" in v) verbose=1 ;; o) output="$OPTARG" ;; i) input="$OPTARG" ;; \?) echo "Unknown option: -$OPTARG" >&2; usage ;; :) echo "Option -$OPTARG requires an argument" >&2; usage ;; esacdoneshift $((OPTIND - 1))[[ -z "$input" ]] && usage(( verbose )) && echo "Processing $input -> $output" >&2
Advanced Idioms & Internals
Lesser-known constructs that separate production scripts from quick hacks.
- ${var:?msg}- Exit with 'msg' printed to stderr if var is unset or empty (self-documenting guard)
- "${arr[@]:2:3}"- Array slicing: 3 elements starting at index 2
- $BASH_SOURCE vs $0- BASH_SOURCE gives the actual file path even when sourced; $0 can be misleading inside sourced files
- exec 3< file / read -u3- Open a file on a custom file descriptor to read it independent of stdin
- wait -n- Wait for the next background job to finish (not all of them) — useful for a job pool
- printf -v var "%s"- Format directly into a variable instead of a subshell command substitution (faster, no fork)
- shopt -s nullglob- Make a non-matching glob expand to nothing instead of the literal pattern string
- coproc- Run a command as a coprocess with bidirectional pipes accessible via an array of file descriptors
Run every script through 'shellcheck' in CI — it catches the exact class of quoting and word-splitting bugs that cause scripts to work in testing but fail unpredictably in production.