Skip to content
Commitpedia
Log in Join & edit

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?

Edit history of Safe bash script template with set ‑euo pipefail and trap: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
bash
Licence
CC0-1.0

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 the ERR trap fire inside functions too.
  • trap cleanup EXIT: runs cleanup however the script ends, whether it succeeds, fails or calls exit. Here it deletes the temp directory, and the guard checks that WORK_DIR is set before deleting anything.
  • trap '...' ERR: prints the failing line number and command, which makes failures much easier to debug.
  • log writes to stderr, so the script’s real output on stdout stays clean for pipes.
  • getopts is built into bash and handles short flags like -v and -h.
  • Exit codes: 0 for success, 1 for a runtime error, 2 for wrong usage, a common convention.

Gotchas of strict mode

  • set -e does not trigger inside an if condition or on the left of && and ||. That is why the template writes if (( VERBOSE )); then ...; fi rather 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 example grep -q pattern file || true.
  • For optional variables, use a default: ${NAME:-default}. A bare $NAME fails under set -u if 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

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +3,610 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?