Reusable patterns for writing shell scripts: boilerplate, flags, config, logging, error handling, and testing.
#!/usr/bin/env bash set -euo pipefail # die on error, unset vars, pipe failures IFS=$'\n\t' # optional: safer word splitting
for arg in "$@"; do case $arg in -v|--verbose) VERBOSE=1 ;; -o|--output) shift; OUT=$1 ;; *) echo "unknown: $arg" >&2; exit 1 ;; esac shift done
$0 script name · $1..$9 args · $@ all args · $# count · $$ PID ·
$? last exit code · $! last background PID
Env-var tunables with defaults:
TURBO="${TURBO:-off}" # ${VAR:-default} die "needs TURBO" # if unset: ${VAR:?message}
[[ -f file ]] [[ -d dir ]] [[ -z $str ]] [[ -n $str ]] [[ $a == $b ]] [[ $a == foo* ]] [[ $n -gt 3 ]] # < -eq -ne -lt -le -ge [[ $x =~ ^[0-9]+$ ]] # regex match command -v foo >/dev/null # command exists
Files: -e exists, -r/-w/-x readable/writable/executable, -s non-empty,
-nt/-ot newer/older.
for f in *.txt; do echo "$f"; done for i in {1..10}; do ...; done while IFS= read -r line; do ...; done < file find . -type f -print0 | while IFS= read -r -d '' f; do ...; done # safe
die() { printf 'Error: %s\n' "$*" >&2; exit 1; } say() { printf '==> %s\n' "$*"; } trap 'echo "interrupted"; exit 130' INT trap cleanup EXIT # run on any exit
Return values: return 1 sets $?. Get output: result=“$(myfunc)”.
${s#prefix} ${s%suffix} # strip shortest ${s##*/} # basename ${s%/*} # dirname ${s/old/new} ${s//o/n} # replace first / all ${#s} # length ${s:2:5} # substring ${s^^} ${s,,} # upper / lower
a=(one two three) echo "${a[1]}" "${a[@]}" "${#a[@]}" a+=(four) mapfile -t lines < file # file -> array
cat <<EOF # expands $vars template for $USER EOF cat <<'EOF' # literal, no expansion no $expansion here EOF
bash -x script.sh # trace every command shellcheck script.sh # linter: catches real bugs set -x; ... ; set +x # trace a section
[: [ is a command with worse semantics.“$var”: unquoted vars split on spaces.cd in a script doesn't affect your shell; use ( cd dir && … ) subshells.local only exists inside functions.exit inside $(…) exits the subshell, not the script.pipefail + set -o at top or foo | grep x returns 0 even when foo fails.read -rp "Name? " name read -rsp "Password: " pw; echo # -s silent read -t 5 -n1 -p "continue? " || exit
[[ $EUID -ne 0 ]] && exec sudo -E bash "$0" "$@" # re-exec via sudo