Not a comprehensive Bash guide. Just the patterns I reach for repeatedly and had to look up too many times before they stuck.
Safe script header
Every script I write starts with:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-e exits on error. -u treats unset variables as errors. -o pipefail catches failures in pipelines (without it, false | true returns 0). The IFS change prevents word splitting on spaces — relevant if you’re iterating over filenames.
Checking if a command exists
if ! command -v jq &>/dev/null; then
echo "jq is required but not installed" >&2
exit 1
fi
Don’t use which — it’s not portable and some systems don’t have it.
Default values for variables
NAME="${1:-world}"
echo "hello, $NAME"
${VAR:-default} uses default if VAR is unset or empty. ${VAR:=default} also assigns the default to VAR. I use the former almost exclusively.
Reading a file line by line
while IFS= read -r line; do
echo "line: $line"
done < input.txt
The IFS= prevents leading/trailing whitespace from being stripped. -r prevents backslash interpretation. This is the safe way — for line in $(cat file) breaks on spaces and is slower.
Temporary files and cleanup
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
# use $tmp safely — it's cleaned up on script exit, even on error
mktemp gives you a guaranteed unique filename in /tmp. The trap on EXIT runs the cleanup whether the script exits normally, on error, or on signal.
Checking if running as root
if [[ $EUID -ne 0 ]]; then
echo "this script must be run as root" >&2
exit 1
fi
Logging with timestamps
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
log "starting deployment"
Simple, no dependencies, readable in cron output.
Running a command with a timeout
timeout 30 some-command || echo "timed out or failed"
timeout is in GNU coreutils — available on any modern Linux. Useful for network calls in scripts where you can’t guarantee the remote responds.
These patterns cover maybe 80% of what I write in Bash. For anything heavier than this, I switch to Python.