====== Bash Recipes ====== Reusable patterns for writing shell scripts: boilerplate, flags, config, logging, error handling, and testing. ===== Skeleton ===== #!/usr/bin/env bash set -euo pipefail # die on error, unset vars, pipe failures IFS=$'\n\t' # optional: safer word splitting ===== Arguments and flags ===== 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} ===== Conditionals ===== [[ -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. ===== Loops ===== 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 ===== Functions / output / errors ===== 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)"''. ===== Strings ===== ${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 ===== Arrays ===== a=(one two three) echo "${a[1]}" "${a[@]}" "${#a[@]}" a+=(four) mapfile -t lines < file # file -> array ===== Heredocs ===== cat < ===== Debugging ===== bash -x script.sh # trace every command shellcheck script.sh # linter: catches real bugs set -x; ... ; set +x # trace a section ===== Gotchas ===== * [[ not ''['': ''['' is a command with worse semantics. * Quote everything: ''"$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. ===== Reading input ===== read -rp "Name? " name read -rsp "Password: " pw; echo # -s silent read -t 5 -n1 -p "continue? " || exit ===== Running as root ===== [[ $EUID -ne 0 ]] && exec sudo -E bash "$0" "$@" # re-exec via sudo ===== See Also ===== * [[terminal:scripting:start|Scripting Index]] * [[terminal:tools:start|CLI Tools]] * [[terminal:start|Return to terminal]] * [[start|Return to wiki home]]