Safe Temp Files in Bash: Trap + Cleanup Pattern for Linux Automation

Last updated on


If your shell script works fine once but fails on the second run, there is a good chance temp file handling is the culprit. In Linux automation, temporary artifacts are easy to ignore at first: one /tmp/result.txt, one lock file, one generated config. But over time, these leftovers create hard-to-debug behavior: stale state, random overwrites, and scripts that look “green” while silently producing invalid output.

This guide shows a practical, production-safe pattern for safe temp files in Bash using trap + cleanup. The goal is simple: your script should be repeatable, predictable, and safe to rerun.

Primary keyword: safe temp files bash trap cleanup linux
Search intent: Best-practice

Why temp file bugs are expensive in production

Temp file issues are rarely dramatic at the start. They accumulate.

Typical failures you will see in real environments:

  • Stale data reuse: an old temp file is read as if it were fresh output.
  • Race condition: two runs use the same temp path and clobber each other.
  • Permission mismatch: a file created by root blocks non-root reruns.
  • Leaking secrets: temporary files contain tokens, then remain world-readable.
  • Disk bloat: abandoned temp directories slowly fill small partitions.

The problem is not “temp files are bad.” The problem is unmanaged lifecycle. A good script defines:

  1. how temp files are created,
  2. who can read them,
  3. when they are deleted,
  4. and what happens on abnormal exits.

The baseline pattern: mktemp + trap + strict mode

A robust baseline for Linux shell automation:

  1. Use mktemp/mktemp -d (never hardcode predictable names).
  2. Enable strict mode: set -euo pipefail.
  3. Register a cleanup function with trap.
  4. Keep temp files under one temp directory per run.
  5. Limit permissions with umask and explicit chmod when needed.

Here is the minimal reusable skeleton:

#!/usr/bin/env bash
set -euo pipefail

umask 077
TMP_DIR="$(mktemp -d -t report-job.XXXXXX)"
LOG_FILE="$TMP_DIR/job.log"
OUTPUT_JSON="$TMP_DIR/output.json"

cleanup() {
  local exit_code=$?
  # Optional debug mode: keep artifacts when DEBUG=1
  if [[ "${DEBUG:-0}" == "1" ]]; then
    echo "[DEBUG] Keeping temp dir: $TMP_DIR"
  else
    rm -rf "$TMP_DIR"
  fi
  exit "$exit_code"
}

trap cleanup EXIT INT TERM

echo "[INFO] temp dir: $TMP_DIR" >> "$LOG_FILE"
# ... your workflow here ...

Why this works:

  • mktemp -d gives you an isolated directory unique to each run.
  • trap ... EXIT INT TERM covers normal exit, Ctrl+C, and termination.
  • umask 077 prevents broad file permissions by default.

A practical workflow example

Assume you fetch metrics, transform them, and publish a report. You need intermediate files, but you do not want leftovers.

#!/usr/bin/env bash
set -euo pipefail

umask 077
TMP_DIR="$(mktemp -d -t metrics-sync.XXXXXX)"
RAW="$TMP_DIR/raw.txt"
NORM="$TMP_DIR/normalized.txt"
FINAL="$TMP_DIR/final.json"

cleanup() {
  local ec=$?
  [[ "${DEBUG:-0}" == "1" ]] || rm -rf "$TMP_DIR"
  exit "$ec"
}
trap cleanup EXIT INT TERM

fetch_metrics() {
  curl -fsS --max-time 8 "https://example.internal/metrics" > "$RAW"
}

normalize_metrics() {
  awk '{print tolower($0)}' "$RAW" > "$NORM"
}

build_json() {
  jq -R -s '{data: split("\n") | map(select(length > 0))}' "$NORM" > "$FINAL"
}

publish() {
  install -m 600 "$FINAL" "/srv/reports/latest-metrics.json"
}

fetch_metrics
normalize_metrics
build_json
publish

echo "Done"

Key points:

  • Intermediate files never leave the temporary directory.
  • Final artifact is copied explicitly to destination with controlled permission.
  • On any error, trap handles cleanup.

Common mistakes (and fixes)

1) Hardcoded temp names

Bad:

TMP_FILE="/tmp/my-script.txt"

This is vulnerable to collisions and accidental reuse. Use mktemp.

2) Cleanup only at the bottom of the script

If your script exits early, that cleanup line never runs. Always use trap.

3) Cleaning with unsafe glob

Bad:

rm -rf /tmp/myjob*

A typo or expansion bug can remove unintended paths. Delete only known variables you created with mktemp.

4) No timeout for network calls

Without timeout, your script can hang and leave state behind. Add --max-time for curl, or use timeout wrapper.

5) Not handling concurrent runs

Even with good temp handling, shared final output can still race. Combine this with lock strategy (flock) when needed.

Related reading:

Production checklist for temp files

Use this checklist before shipping automation to production:

  • Temp paths created with mktemp/mktemp -d
  • Strict mode enabled (set -euo pipefail)
  • trap cleanup EXIT INT TERM defined
  • umask set (usually 077 for sensitive data)
  • All intermediate artifacts kept in per-run temp directory
  • Destination writes use explicit permission (install -m 600 or equivalent)
  • External calls have timeout and retry policy
  • Concurrency strategy documented (flock if needed)
  • Debug mode can preserve temp files intentionally

This checklist is lightweight but catches most recurring shell-script incidents in small teams.

Advanced pattern: keep temp on failure only

Sometimes you need forensic debugging when a job fails. You can preserve temp files only on non-zero exit:

cleanup() {
  local ec=$?
  if [[ $ec -ne 0 ]]; then
    echo "[WARN] failed; keeping temp dir for investigation: $TMP_DIR" >&2
  else
    rm -rf "$TMP_DIR"
  fi
  exit "$ec"
}

This gives you the best of both worlds:

  • clean system on success,
  • useful evidence on failure.

For CI/CD jobs, you can also archive $TMP_DIR as an artifact when ec != 0.

Security notes you should not skip

Temp files often store transformed payloads, environment dumps, token-bearing headers, or partial config. Treat them as sensitive unless proven otherwise.

Minimum security baseline:

  • Set umask 077 near the top of script.
  • Never write secrets to logs unless masked.
  • Avoid temp locations shared across untrusted users.
  • Use install -m for final file permissions.
  • Remove debug leftovers after incident is resolved.

If your script touches credentials, do a quick threat model: “What if another local user reads /tmp during execution?” This one question prevents many avoidable leaks.

FAQ (Schema-Ready)

How do I choose between mktemp file and mktemp -d directory?

Use mktemp -d when your workflow has multiple intermediate artifacts. It simplifies cleanup and isolates one run from another. Use single-file mktemp only for very small scripts.

Does trap always run on script termination?

trap runs for handled signals and normal exits, but not for every possible kill scenario (for example, hard kill conditions). It still covers the majority of operational failures and should be your default.

Can I skip cleanup in development?

Yes, with explicit debug mode (DEBUG=1) so behavior is intentional. Do not disable cleanup silently in production.

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "How do I choose between mktemp file and mktemp -d directory?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Use mktemp -d for multi-step workflows with multiple artifacts. It keeps each run isolated and cleanup easier."
      }
    },
    {
      "@type": "Question",
      "name": "Does trap always run on script termination?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Trap covers normal exits and configured signals like INT and TERM, but not every hard-stop scenario. It remains the recommended baseline."
      }
    },
    {
      "@type": "Question",
      "name": "Can I skip cleanup in development?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, use an explicit DEBUG flag to preserve temporary files intentionally, and keep cleanup enabled by default for production."
      }
    }
  ]
}

Conclusion

Reliable Linux automation is less about writing longer scripts and more about controlling side effects. Temp files are one of the biggest side effects in Bash jobs. With a small baseline—mktemp, strict mode, and trap cleanup—you remove a surprising amount of production risk.

If you want scripts that are safe to rerun, easier to debug, and less likely to break at 3 AM, start by treating temp file lifecycle as a first-class design decision, not an afterthought.

Komentar

Real-time

Memuat komentar...

Tulis Komentar

Email tidak akan ditampilkan

0/2000 karakter

Catatan: Komentar akan dimoderasi sebelum ditampilkan. Mohon bersikap sopan dan konstruktif.