Find and prevent bugs in your scripts: trace execution with set -x, check syntax without running, lint with ShellCheck, and adopt the habits that keep scripts robust.
Why: set -x prints each command, with its variables expanded, just before it runs — so you can see exactly what the shell is doing and where reality diverges from your mental model. Turn it off again with set +x to trace only a suspect section.
#!/usr/bin/env bash
set -x # start tracing
name="Ada"
greeting="Hello, $name"
set +x # stop tracing
echo "$greeting"
# The trace shows: + name=Ada / + greeting='Hello, Ada'Why: bash -n parses the script and reports syntax errors without executing a single command — a safe pre-flight check for a script that does destructive work. It catches an unclosed quote or a missing fi before they bite.
Parse and report syntax errors, but run nothing
bash -n deploy.shCombine with -x to trace a dry parse of control flow
bash -nx deploy.shWhy: ShellCheck is a static analyzer that catches the bugs Bash silently tolerates — unquoted variables, useless cat, wrong test operators — with a clear explanation for each. Run it on every script; it is the highest-value habit in this course. See shellcheck.net.
Analyze a script for common mistakes
shellcheck deploy.shExample finding: SC2086: Double quote to prevent globbing and word splitting. rm $file ^--^ SC2086
Note: the habits that separate a throwaway script from one you can trust. Start with set -euo pipefail. Quote every "$variable". Use local in functions. Clean up with trap. Check that required arguments and files exist before acting. Run shellcheck before you ship.
#!/usr/bin/env bash
set -euo pipefail # fail fast, fail loud
cleanup() { rm -f "$tmp"; }
trap cleanup EXIT # always tidy up
main() {
local input="${1:?Usage: $0 <file>}" # required argument
[[ -f "$input" ]] || { echo "no such file: $input" >&2; exit 1; }
tmp=$(mktemp)
# ...real work here, every "$variable" quoted...
}
main "$@"