Stage 2 · Tools
Bash Fundamentals
Script Structure & Shebang
Script headers, error handling, set options, and the main pattern for production scripts.
The Script Header
Every production script should start with a consistent header that includes the shebang, description, and strict mode settings. This header sets the tone for reliable, maintainable scripts.
#!/usr/bin/env bash
#
# script-name.sh — Brief description
#
# Usage: script-name.sh [options] <arguments>
#
# Author: Your Name
# Date: 2025-01-15
set -euo pipefailThe header includes the shebang for portability, a description block for documentation, and set -euo pipefail for strict error handling.
set Options
The set command controls shell behavior. These options are the difference between scripts that fail silently and scripts that catch errors immediately.
| Option | Short | Meaning |
|---|---|---|
| -e | errexit | Exit immediately if any command fails |
| -u | nounset | Error on undefined variables |
| -o pipefail | pipefail | Pipeline returns exit code of last failed command |
| -x | xtrace | Print each command before executing (debugging) |
| -v | verbose | Print shell input lines as they are read |
# Without pipefail — hides errors
set -e
echo "test" | grep "missing" | cat
echo "Still running!" # This runs even though grep failed
# With pipefail — catches errors
set -eo pipefail
echo "test" | grep "missing" | cat
echo "This never runs" # Script exits because grep returned 1Without pipefail, only the exit code of the last command in a pipeline matters. With pipefail, if any command in the pipeline fails, the entire pipeline fails.
This three-option combination catches the most common shell script bugs: unhandled errors (-e), undefined variables (-u), and silent pipeline failures (-o pipefail). Make it a habit to include it in every script.
Error Handling
With set -e, your script exits on error. But sometimes you need to clean up resources or provide helpful error messages. The trap command handles this.
TMPDIR=""
cleanup() {
if [[ -n "$TMPDIR" && -d "$TMPDIR" ]]; then
rm -rf "$TMPDIR"
echo "Cleaned up $TMPDIR"
fi
}
trap cleanup EXIT
TMPDIR=$(mktemp -d)
echo "Working in $TMPDIR"
# ... do work ...
# Cleanup runs automatically on exit, even on errorsThe EXIT trap runs when the script exits for any reason — normal completion, error, or signal. This guarantees cleanup happens. Other common signals to trap: INT (Ctrl+C), TERM (kill), ERR (any command failure).
The main() Pattern
Wrapping your script logic in a main() function provides clear structure, allows testing, and controls execution flow. This is the standard pattern for production Bash scripts.
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-v) VERBOSE=true; shift ;;
-h) usage; exit 0 ;;
--) shift; break ;;
*) echo "Unknown: $1" >&2; usage >&2; exit 1 ;;
esac
done
}
main() {
parse_args "$@"
setup
do_work
cleanup
}
main "$@"parse_args handles command-line parsing. main() orchestrates the flow. main "$@" passes all script arguments to main. This pattern separates argument parsing from business logic.
Production Script Template
Here is a complete template that combines all the patterns from this lesson. Use it as a starting point for new scripts.
#!/usr/bin/env bash
#
# script-name.sh — Description
#
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "{BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "{BASH_SOURCE[0]}")"
TMPDIR=""
VERBOSE=false
cleanup() {
[[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"
}
trap cleanup EXIT
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS] <args>
Options:
-v Verbose output
-h Show this help
Example:
$SCRIPT_NAME -v arg1 arg2
EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-v) VERBOSE=true; shift ;;
-h) usage; exit 0 ;;
--) shift; break ;;
*) break ;;
esac
done
if [[ $# -lt 1 ]]; then
echo "Error: Missing required arguments" >&2
usage >&2
exit 1
fi
}
main() {
parse_args "$@"
TMPDIR=$(mktemp -d)
log_info "Starting with args: $*"
# Your logic here
}
main "$@"This template includes: shebang, strict mode, readonly constants, cleanup trap, usage function, argument parsing, and main function. Adapt it for your needs.
If your script exceeds 500 lines, consider breaking it into smaller scripts or moving functions to library files. Shorter scripts are easier to review, debug, and maintain.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.