folk.zone Wiki

a public knowledge base

Site Tools


terminal:scripting:bash_recipes

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 <<EOF        # expands $vars
template for $USER
EOF
cat <<'EOF'      # literal, no expansion
no $expansion here
EOF

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/bash_recipes.txt · Last modified: by 127.0.0.1