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:
- how temp files are created,
- who can read them,
- when they are deleted,
- and what happens on abnormal exits.
The baseline pattern: mktemp + trap + strict mode
A robust baseline for Linux shell automation:
- Use
mktemp/mktemp -d(never hardcode predictable names). - Enable strict mode:
set -euo pipefail. - Register a cleanup function with
trap. - Keep temp files under one temp directory per run.
- Limit permissions with
umaskand 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 -dgives you an isolated directory unique to each run.trap ... EXIT INT TERMcovers normal exit, Ctrl+C, and termination.umask 077prevents 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,
traphandles 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:
- Mencegah Cron Job Tumpang Tindih di Linux dengan flock
- Shell Script Retry, Backoff, Timeout Patterns Linux Automation
- Bash getopts: Parse CLI Arguments Linux Production
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 TERMdefined -
umaskset (usually077for sensitive data) - All intermediate artifacts kept in per-run temp directory
- Destination writes use explicit permission (
install -m 600or equivalent) - External calls have timeout and retry policy
- Concurrency strategy documented (
flockif 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 077near the top of script. - Never write secrets to logs unless masked.
- Avoid temp locations shared across untrusted users.
- Use
install -mfor 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
Memuat komentar...
Tulis Komentar