Safe bash script template with set ‑euo pipefail and trap
A starting point for bash scripts that stops on errors, rejects unset variables, cleans up temp files on exit and parses options. Copy it, then replace the work section.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
What this changes
Creates a temporary folder with mktemp -d, writes a sorted copy of the input file into it, prints a line count and deletes the folder when it exits. The input file is only read.
How to undo it
Nothing to undo: the temporary folder is removed automatically, even on errors.
By default bash keeps going after a command fails, treats a misspelled variable as an empty string and ignores errors in the middle of a pipeline. That is fine at an interactive prompt but dangerous in a script. This template turns on stricter behavior and adds the pieces almost every script needs: usage text, logging to stderr, option parsing and guaranteed cleanup.
The template
Check before you run this
- Deletes files and folders without asking. Check the path before running.
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_NAME=$(basename "$0")
WORK_DIR=""
usage() {
cat <<USAGE
Usage: $SCRIPT_NAME [-v] [-h] <input-file>
-v verbose output
-h show this help
USAGE
}
log() { printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" >&2; }
die() { log "ERROR: $*"; exit 1; }
cleanup() {
local status=$?
if [[ -n "$WORK_DIR" && -d "$WORK_DIR" ]]; then
rm -rf -- "$WORK_DIR"
fi
exit "$status"
}
trap cleanup EXIT
trap 'log "failed at line $LINENO: $BASH_COMMAND"' ERR
VERBOSE=0
while getopts ":vh" opt; do
case "$opt" in
v) VERBOSE=1 ;;
h) usage; exit 0 ;;
\?) usage >&2; die "unknown option: -$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
[[ $# -eq 1 ]] || { usage >&2; exit 2; }
INPUT=$1
[[ -f "$INPUT" ]] || die "file not found: $INPUT"
WORK_DIR=$(mktemp -d)
if (( VERBOSE )); then log "temp dir: $WORK_DIR"; fi
# Your real work goes here.
sort -u -- "$INPUT" > "$WORK_DIR/unique.txt"
log "$(wc -l < "$WORK_DIR/unique.txt" | tr -d ' ') unique lines in $INPUT"
How to use it
chmod +x template.sh
./template.sh -v names.txt
./template.sh -h
The demo work section prints how many unique lines a file has. Replace it with your own logic and rename the script.
What each part does
set -e: exit as soon as a command fails, instead of carrying on with bad state.set -u: using an unset variable is an error, which catches typos like$OUPUT.set -o pipefail: a pipeline fails if any stage fails, not only the last one.set -E: lets theERRtrap fire inside functions too.trap cleanup EXIT: runscleanuphowever the script ends, whether it succeeds, fails or callsexit. Here it deletes the temp directory, and the guard checks thatWORK_DIRis set before deleting anything.trap '...' ERR: prints the failing line number and command, which makes failures much easier to debug.logwrites to stderr, so the script’s real output on stdout stays clean for pipes.getoptsis built into bash and handles short flags like-vand-h.- Exit codes:
0for success,1for a runtime error,2for wrong usage, a common convention.
Gotchas of strict mode
set -edoes not trigger inside anifcondition or on the left of&&and||. That is why the template writesif (( VERBOSE )); then ...; firather than(( VERBOSE )) && .... As the last line of a function, the short form would make the function return a failure status when the condition is false.- Commands that are allowed to fail need
|| true, for examplegrep -q pattern file || true. - For optional variables, use a default:
${NAME:-default}. A bare$NAMEfails underset -uif it is unset. - Always quote variables (
"$INPUT"), and pass--before file names so names starting with-are not read as options.
Run ShellCheck on your scripts. It catches quoting mistakes and many of the traps above before they bite. The template works with bash 3.2, which macOS still ships, as well as bash 5.
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0059 Incremental snapshot backups with rsync A bash script that copies a folder into dated snapshots with rsync, hard-links unchanged files to the previous snapshot to save space,… Advanced · Step by step · Linux, macOS Script Backups & syncing · Shell scripting 15 Oct 2026
- 0061 Fix: Address already in use (free a busy port) Your server cannot start because another process already listens on its port. This script shows which process holds the port and stops… Intermediate · Fixes an error · Linux, macOS Script Processes & ports · Shell scripting 15 Oct 2026
- 0108 List all Proxmox VE guests with status and locks (pvesh + jq) A Bash script that lists every VM and container in the cluster with node, status, lock, memory and uptime, using the Proxmox… Intermediate · Cheat sheet · Linux Script Shell scripting · VMs & containers 15 Oct 2026
- 0109 A vzdump wrapper script with checks, retention and a log file A Bash wrapper around vzdump that checks the target storage first, backs up chosen guests or all guests with zstd and keep-last… Intermediate · Step by step · Linux Script Backup & restore · Shell scripting 15 Oct 2026
- 0265 Turn a one-liner into a robust bash script: strict mode, options, logging, traps, locking and a schedule Grow a find-and-gzip one-liner step by step into a dependable bash script with strict mode, getopts, usage text, logging, error traps, dry-run,… Intermediate · Guided project · Linux, macOS Article Services & systemd · Shell scripting 15 Oct 2026
- 0335 Docker cleanup script with dry run and confirmation A bash wrapper around docker system prune that shows disk usage and lists what would be removed, supports a dry-run flag, and… Beginner · Step by step · Linux, macOS Script Docker basics · Shell scripting 15 Oct 2026
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +3,610 chars