Bash Scripting cheat sheet
A scannable Bash Scripting reference: 16 short snippets across 9 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Variables, quoting and exit codes | An unquoted expansion is split on whitespace and then treated as a glob pattern. That is the source of most problems | lesson |
| Writing a robust script | Strict mode, traps that clean up after a crash, and argument parsing that fails with a useful message | lesson |
| Shells, startup files and running scripts | A shell is a program that reads commands and runs them. The same script behaves differently depending on which shell | lesson |
| Parameter expansion and string manipulation | The patterns are globs, not regular expressions: *, ? and [a-z]. Use #/% for path surgery and reach for sed only when | lesson |
| Text processing: grep, sed and awk in scripts | Under set -e, a 1 from grep aborts the script even though "no match" is a legitimate result. Put the call in an if or | lesson |
| Process handling, signals and parallelism | Background jobs and wait, job control, signals and traps, timeouts, xargs -P, GNU parallel, and controlling concurrency | lesson |
| Testing and linting shell scripts | Treat every suppression as a comment that needs a reason. A file with a dozen unexplained disables is linted in name | lesson |
| Portability and POSIX shell | A script that runs on your laptop may run under dash in a Debian container, busybox ash in an Alpine image, or bash 3.2 | lesson |
| A practical automation project | Idempotency is the property that makes automation safe to retry. Aim for every step to be either naturally repeatable | lesson |
Quick snippets
Variables, quoting and exit codes
Variables
name=ada # no spaces around =
count=3
files=$(ls -1 | wc -l) # command substitution
today=$(date +%F)
echo "$name has $count items" # double quotes expand
echo '$name is literal' # single quotes do not
readonly MAX_RETRIES=5
export SERVICE_URL="https://api.example.com" # visible to child processes
Quoting and word splitting
file="my report.txt"
cat $file # two arguments: "my" and "report.txt"
cat "$file" # one argument: the whole name
rm *.txt # the glob expands in the current directory
rm "*.txt" # tries to delete a file literally named *.txt
set -- one "two three"
for arg in "$@"; do echo "[$arg]"; done # keeps each argument intactFull lesson: Variables, quoting and exit codes →
Writing a robust script
Strict mode
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t' # a safer default separator for unquoted expansions
Traps and cleanup
tmpdir=$(mktemp -d)
cleanup() {
local status=$?
rm -rf "$tmpdir"
exit "$status"
}
trap cleanup EXIT INT TERM
trap 'echo "error on line $LINENO" >&2' ERR
exec {lock_fd}>/var/lock/myscript.lock || { echo "already running" >&2; exit 1; }Full lesson: Writing a robust script →
Shells, startup files and running scripts
Which shell is running
echo "$0" # name of the running shell or script
echo "$BASH_VERSION" # set only under bash
ps -p $$ -o comm= # the executable behind this shell
ls -l /bin/sh # on Debian/Ubuntu this is usually a symlink to dash
bash --version
sh --version 2>/dev/null || echo "sh is not bash"
Shebangs, permissions and startup files
#!/usr/bin/env bash
# ^ the kernel runs the named interpreter, not your login shell
chmod +x deploy.sh
./deploy.sh # honoured: shebang decides the interpreter
bash deploy.sh # ignores the shebang, forces bash
sh deploy.sh # forces sh: bashisms break here
source deploy.sh # run in the CURRENT shell: variables and cd persist
. ./deploy.sh # identical, POSIX spelling
Shebangs, permissions and startup files
# make a login shell pick up interactive settings
[[ -f ~/.bashrc ]] && . ~/.bashrc
# and keep interactive-only code out of scripts
case $- in
*i*) ;;
*) return ;; # not interactive: stop here
esacFull lesson: Shells, startup files and running scripts →
Parameter expansion and string manipulation
Defaults, errors and alternatives
: "${API_URL:?API_URL must be set}" # abort with your own message
name=${1:-guest} # use the default if unset OR empty
count=${COUNT:=10} # default, and assign it for later
quiet=${VERBOSE:+no} # 'no' when VERBOSE is set and non-empty
raw=${MAYBE_BLANK-unchanged} # '-' form: only an UNSET variable triggers it
# the ':' forms also treat the empty string as missing; without it they do not
echo "${EMPTY:-fallback}" # fallback
echo "${EMPTY-fallback}" # (empty line: the variable exists, so it is used)Full lesson: Parameter expansion and string manipulation →
Text processing: grep, sed and awk in scripts
sed: substitutions and edits
sed -n '10,20p' file # print only a range
sed '/^$/d' file # delete blank lines
sed 's/foo/bar/' file # first match per line
sed 's/foo/bar/g' file # every match
sed -E 's/([0-9]+)ms/\1 ms/' file # capture group, with -E
sed -e 's/a/b/' -e 's/c/d/' file # several expressions
sed -i.bak 's/localhost/db.internal/g' config.ini # edit in place, keep a backup
sed '1i # managed by automation' file > out # insert a line at the top
awk, cut, sort and choosing
awk '{ print $1, $3 }' access.log # select columns
awk -F, '{ sum += $3 } END { print sum }' data.csv
awk '$9 >= 500 { print $7 }' access.log # filter by a field
awk -F: 'NR > 1 && $3 >= 1000 { print $1 }' /etc/passwd
awk '/ERROR/ { errors++ } END { print errors + 0 }' app.log
cut -d, -f1,3 data.csv # fixed columns, fast
sort -t, -k3,3nr data.csv # numeric, descending, third field
sort file | uniq -c | sort -rn | head # top repeated lines
tr -s ' ' '\t' < file # squeeze spaces into tabsFull lesson: Text processing: grep, sed and awk in scripts →
Process handling, signals and parallelism
Signals, traps and timeouts
trap 'echo "interrupted" >&2; exit 130' INT
trap 'kill 0' TERM # forward the signal to the whole process group
trap 'cleanup' EXIT # runs on success, failure and signals
trap - INT # reset a handler to the default
timeout 30s curl -fsS "$url" # SIGTERM after 30s
timeout -s KILL 5m rsync -a src/ dst/ # hard kill after 5 minutes
timeout --preserve-status 10s cmd # report the command's status, not 124
curl -fsS --max-time 10 --connect-timeout 3 "$url" # timeouts at the client tooFull lesson: Process handling, signals and parallelism →
Testing and linting shell scripts
ShellCheck and shfmt
shellcheck deploy.sh # every finding, with a wiki link
shellcheck -S warning bin/*.sh # fail only on warning and above
shellcheck -x -s bash lib/*.sh # follow sourced files, force a dialect
shellcheck -f gcc -e SC1091 script.sh # CI-friendly output, one rule disabled
shfmt -d -i 2 -ci script.sh # show a diff of the formatting
shfmt -w -i 2 -ci script.sh # rewrite in place
# .shellcheckrc in the repository root
# shell=bash
# disable=SC1091 # not following a source we know about
# external-sources=true
Fixtures and CI
# fixtures: real-looking inputs under version control
test/fixtures/invoices/2026-08.csv
test/fixtures/config/minimal.ini
# never write into the checkout; use a temp directory per run
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"' EXIT
# a pre-commit hook that is fast enough to actually leave enabled
shellcheck -S warning bin/*.sh || exit 1
bash -n bin/*.sh || exit 1
bats --print-output-on-failure test/Full lesson: Testing and linting shell scripts →
Portability and POSIX shell
Which shell is really there
ls -l /bin/sh # dash on Debian/Ubuntu, ash on Alpine
busybox --list | head # what a minimal container actually has
bash --version | head -n 1 # 3.2 on macOS: no associative arrays
dash -n script.sh # syntax check under a strict POSIX shell
checkbashisms script.sh # report bash-only constructs
shellcheck -s sh script.sh # lint against the POSIX dialectFull lesson: Portability and POSIX shell →
A practical automation project
Structure, logging and dry-run
bin/deploy entry point: arg parsing, then calls lib functions
lib/common.sh log, die, run, config loading
lib/deploy.sh the actual work, one function per step
test/ bats tests and fixtures
README.md usage, configuration, exit codes
deploy.conf.example documented sample configuration
Finishing and shipping it
# exit codes your callers can branch on
# 0 success (or nothing to do)
# 1 unexpected runtime failure
# 2 usage error
# 3 another instance is running
# 4 configuration invalid
trap 'status=$?; cleanup; exit "$status"' EXIT INT TERM
trap 'log "failed at line $LINENO" >&2' ERR
if $verbose; then set -x; fi
log "starting $PROG $VERSION env=$env target=$target dry_run=$DRY_RUN"Full lesson: A practical automation project →
FAQ
Is this Bash Scripting cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Git Linux Docker Kubernetes Nginx CI / CD
Last refreshed 2026-09-27.