Temp Files, Cleanup Traps, and Logging
Creating scratch files safely with mktemp, guaranteeing cleanup even when a script fails partway through using trap, and writing logs that are actually useful for later debugging.
What you'll learn
- Explain why mktemp is safer than manually constructing a temp file name
- Explain how trap guarantees cleanup code runs even if the script exits early or fails
- Design log output that includes enough context to debug a failure after the fact
Prerequisites
Explanation
Every real command below runs only in your own terminal — this lesson's exercises model these decisions as data, never executing a real script.
A script that needs a scratch file should never construct a name manually (like /tmp/myscript-temp.txt) — a fixed, predictable name is both a real correctness bug (two concurrent runs of the same script collide and corrupt each other's data) and, in some contexts, a security concern (a predictable path in a shared directory like /tmp can be pre-created by another user to intercept or manipulate the script's data). mktemp solves this by creating a file with a guaranteed-unique, randomized name and returning that name — the script never has to invent or predict the name itself, eliminating both problems at once.
trap registers a command (or function) to run automatically when the script receives a specified signal, or exits for any reason at all — the most common and valuable pattern is trap cleanup EXIT, which guarantees a cleanup function runs whether the script finishes normally, fails partway through, or is interrupted by the user. This matters specifically because a script that only deletes its temp file at the end of its normal execution path will leak that file every single time it exits early — from an error, a set -e failure, or a Ctrl-C — silently accumulating scratch files over time. Registering cleanup on EXIT closes this gap, because EXIT fires in every one of those cases, not just the success path.
Good logging is not simply "print more" — it's printing the right context so a failure is diagnosable after the fact, when the person debugging it wasn't watching the terminal live. That means: a timestamp (so log lines can be correlated with when something else happened), enough identifying detail to know exactly what was being attempted (not just "failed" but "failed to copy X to Y"), and a clear distinction between routine informational output and an actual error — conventionally achieved by sending errors to stderr specifically (not mixed into stdout), so a log-processing pipeline or a human skimming output can immediately tell them apart.
Example
Modeling why a fixed temp-file name is unsafe, and how a trap-registered cleanup differs from an end-of-script-only cleanup.
function wouldCollide(scriptRuns) {
// A FIXED temp file name: every concurrent run uses the exact same path.
const fixedNamePaths = scriptRuns.map(() => "/tmp/myscript-temp.txt");
const uniqueFixedPaths = new Set(fixedNamePaths);
return uniqueFixedPaths.size < scriptRuns.length; // true if any two runs collided
}
console.log(wouldCollide(["run1", "run2"])); // true -- both runs used the identical fixed path
function mktempStyleNames(scriptRuns) {
// mktemp-style: each call gets a genuinely unique, randomized suffix.
return scriptRuns.map((_, i) => "/tmp/myscript." + (1000 + i) + "." + Math.random().toString(36).slice(2));
}
const uniqueMktempPaths = new Set(mktempStyleNames(["run1", "run2"]));
console.log(uniqueMktempPaths.size === 2); // true -- no collision, by construction
function cleansUpOnEveryExit(exitPath, hasTrapOnExit) {
// Only the "trap cleanup EXIT" pattern cleans up on every exit path, not only the success path.
if (exitPath === "success") return true; // both approaches clean up on a normal finish
return hasTrapOnExit; // an early error or interruption is ONLY cleaned up if trap was registered
}
console.log(cleansUpOnEveryExit("early-error", false)); // false -- leaks the temp file
console.log(cleansUpOnEveryExit("early-error", true)); // true -- trap on EXIT still firesTry it yourself
Call cleansUpOnEveryExit with exitPath 'interrupted' and hasTrapOnExit true, and confirm cleanup still fires.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
This models unique-temp-name generation only -- no real file is created. Write makeTempName(prefix, existingNames), returning prefix + '-' + n for the smallest positive integer n such that the result is not already in existingNames.
Checks: generates the first name correctly when nothing exists yet · skips a single already-used name · skips multiple already-used names in sequence
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
This models a structured log-line formatter only -- no real logging occurs. Write formatLogLine(timestamp, level, message), returning '[' + timestamp + '] ' + level.toUpperCase() + ': ' + message. Write shouldGoToStderr(level), returning true only for 'error' or 'warn' (case-insensitive).
Checks: formats a structured, timestamped log line correctly · correctly routes an error-level message to stderr, regardless of case · correctly keeps an informational message off of stderr
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Manually constructing a temp file path (like /tmp/script-temp.txt) instead of using mktemp -- a fixed, predictable name risks collisions between concurrent runs and is a real correctness (and sometimes security) problem.
- Only deleting a temp file at the end of the script's normal, successful path -- this leaks the file every single time the script exits early, from an error, a set -e failure, or an interruption.
- Logging errors to stdout instead of stderr -- this makes it impossible for a log-processing pipeline (or a human skimming output) to reliably distinguish routine informational output from an actual failure.
Knowledge check
Takeaway
Use mktemp instead of a manually constructed temp file name to avoid collisions; register cleanup with trap cleanup EXIT so it runs on every exit path, not only the successful one; and route error-level output to stderr specifically so it stays distinguishable from routine informational output.
Summary
mktemp creates a guaranteed-unique scratch file, avoiding the collision and predictability risks of a manually constructed name. trap cleanup EXIT guarantees cleanup code runs on every exit path -- success, error, or interruption -- not only the normal finish. Useful logs include a timestamp and enough context to debug after the fact, with errors routed to stderr specifically so they stay distinguishable from routine output.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.