mktemp — Safe Temporary Files and Directories

Create unique temporary files and directories safely. Avoids race conditions and predictable names by generating a random suffix. Essential for shell scripts that need scratch space. Syntax differs between GNU coreutils (Linux) and BSD (macOS).

mktemp creates temporary files and directories with a guaranteed-unique name, solving a real security problem: predictable names like /tmp/$$.tmp invite symlink attacks and race conditions, because an attacker can guess the name and plant a trap in /tmp ahead of time. Instead, mktemp generates a random suffix, creates the file atomically with mode 600 (directories with 700), and hands you back the path. Use -d to get a directory instead of a file. This guide covers the basics, templates, the differences between GNU (Linux) and BSD (macOS), and the indispensable cleanup with trap.

Basic Usage

mktemp — Create a unique temporary file in $TMPDIR (or /tmp). Returns the full path on stdout.

mktemp
# /tmp/tmp.A3kLp9

mktemp -d — Create a unique temporary directory instead of a file.

mktemp -d
# /tmp/tmp.xY7qZ2

<var>=$(mktemp) — Capture the path into a shell variable for later use.

tmp=$(mktemp)
echo "log data" > "$tmp"

<var>=$(mktemp -d) — Capture a fresh temp directory path into a variable.

workdir=$(mktemp -d)
cd "$workdir"

mktemp --help — Show all available options with short descriptions.

mktemp --help

Templates (XXX Placeholders)

mktemp <template> — Use a custom name template. The trailing X characters (at least 3) are replaced with random characters.

mktemp mydata.XXXXXX
# mydata.k7Bq2p

mktemp <prefix>.XXXXXXXXXX — More X characters means more entropy and lower collision probability. 10+ recommended for long-lived files.

mktemp build.XXXXXXXXXX
# build.Hf3k9pXw2L

mktemp -d <template> — Create a directory using a custom template.

mktemp -d myapp-XXXXXX
# myapp-9bTq2K

mktemp --suffix=<ext> <template> — Append a suffix after the random part. Useful for files that need a specific extension. GNU coreutils only.

mktemp --suffix=.json data.XXXXXX
# data.k7Bq2p.json

mktemp <prefix>XXXXXX<suffix> — BSD/macOS alternative: place XXX in the middle of the name for a literal suffix.

mktemp build-XXXXXX.tar.gz
# build-k7Bq2p.tar.gz

Directory Options

mktemp -p <dir> — Create the temp file/directory inside

instead of $TMPDIR. GNU coreutils.

mktemp -p /var/tmp
# /var/tmp/tmp.A3kLp9

mktemp --tmpdir=<dir> <template> — Long form of -p. Combine with a template.

mktemp --tmpdir=/var/cache app.XXXXXX

mktemp -t <template> — Place the file in $TMPDIR (or a system default). Behaviour differs: on Linux -t is mostly a legacy alias; on BSD/macOS it is commonly used to set the prefix.

mktemp -t myapp

TMPDIR=<dir> mktemp — Override the default temp directory via environment variable for a single command.

TMPDIR=/fast/ssd mktemp -d

mktemp -p "" <template> — Force relative placement: create in the current working directory instead of $TMPDIR.

mktemp -p "" cache-XXXXXX

Other Flags

mktemp -u — Dry run: print a unique name without creating the file. UNSAFE — the name may be taken by another process before you use it. Avoid for real work.

mktemp -u
# /tmp/tmp.A3kLp9 (not created)

mktemp --dry-run — Long form of -u. Same warning applies: only safe for preview purposes.

mktemp --dry-run --suffix=.log test.XXXXXX

mktemp -q — Quiet mode. Suppress error messages if creation fails. Useful in scripts with custom error handling.

tmp=$(mktemp -q) || exit 1

mktemp --version — Show the installed mktemp version. On Linux this tells you the coreutils release; on macOS it reports the BSD build.

mktemp --version

GNU (Linux) vs BSD (macOS)

mktemp (portable) — Plain form with no template works on both GNU and BSD. Safest default if you do not need a custom prefix.

tmp=$(mktemp) || exit 1

mktemp -t <prefix> — GNU: prefix is optional; path goes to $TMPDIR. BSD/macOS: -t REQUIRES a prefix and inserts it before the random suffix.

# macOS:
mktemp -t myapp
# /var/folders/.../myapp.XXXXXX

mktemp <prefix>.XXXXXX — Template form works on both. Must contain at least 3 X's at the END of the filename component on BSD (suffix via explicit characters after XXXXXX).

mktemp app.XXXXXX       # both
mktemp app-XXXXXX.log  # BSD ok, GNU needs --suffix

mktemp --suffix=.ext — GNU only. BSD/macOS does NOT support --suffix. For cross-platform scripts use an inline suffix: prefix-XXXXXX.ext.

# Linux only:
mktemp --suffix=.sql dump.XXXXXX

mktemp -p <dir> — GNU only. BSD does not support -p. For cross-platform scripts pass the directory inside the template or set TMPDIR.

# Portable:
TMPDIR=/var/tmp mktemp

Cleanup Patterns (trap)

trap 'rm -f "$tmp"' EXIT — Remove a temp file when the script exits — normally, on error, or on Ctrl+C. Set the trap right after creating the file.

tmp=$(mktemp) || exit 1
trap 'rm -f "$tmp"' EXIT

trap 'rm -rf "$dir"' EXIT — Recursive cleanup for a temp directory. Use -rf because the dir will usually contain files by the time the trap fires.

dir=$(mktemp -d) || exit 1
trap 'rm -rf "$dir"' EXIT

cleanup() { rm -rf "$dir"; }; trap cleanup EXIT — Use a function for more complex cleanup (close fds, kill background jobs, unmount loop devices, then remove the dir).

cleanup() {
  [[ -n $pid ]] && kill $pid 2>/dev/null
  rm -rf "$dir"
}
trap cleanup EXIT

trap '...' EXIT INT TERM HUP — Also clean up on common signals. EXIT alone is enough in bash (EXIT fires after signals too), but explicit signals help in POSIX sh.

trap 'rm -rf "$dir"' EXIT INT TERM HUP

tmp=$(mktemp) || { echo "mktemp failed" >&2; exit 1; } — Always check that mktemp succeeded before trusting the variable. Failure is rare but possible (full disk, bad permissions).

tmp=$(mktemp) || { echo "mktemp failed" >&2; exit 1; }
trap 'rm -f "$tmp"' EXIT

Common Use Cases

Scratch file for command output — Capture output for later processing without risking a name collision in /tmp.

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
curl -s https://example.com > "$tmp"
grep -c "<h1>" "$tmp"

Atomic file replacement — Write to a temp file in the same directory, then rename on top of the target. Rename is atomic on the same filesystem.

tmp=$(mktemp -p "$(dirname "$target")")
./generate > "$tmp" && mv "$tmp" "$target"

Sorted/deduplicated edit in place — sort -o / sed -i exist, but mktemp gives you the pattern for tools that lack in-place support.

tmp=$(mktemp)
sort -u input.txt > "$tmp" && mv "$tmp" input.txt

Temp workspace for extraction — Unpack an archive in an isolated directory, inspect it, then clean up automatically.

dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
tar -xzf archive.tar.gz -C "$dir"
find "$dir" -name '*.conf'

Named pipe alternative via temp file — Pass two command outputs to a tool that expects two files (diff, cmp, comm).

a=$(mktemp); b=$(mktemp)
trap 'rm -f "$a" "$b"' EXIT
curl -s url1 > "$a"
curl -s url2 > "$b"
diff "$a" "$b"

Temporary named pipe (FIFO) — mktemp creates a regular file; for a FIFO, create a temp DIR with mktemp -d, then mkfifo inside it.

dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
mkfifo "$dir/pipe"
producer > "$dir/pipe" &
consumer < "$dir/pipe"

Security & Gotchas

Default mode is 0600 (600) — mktemp creates files with mode 600 (rw for owner only) and directories with 700. This is the main reason to use mktemp over ad-hoc /tmp/$$ patterns.

stat -c %a "$(mktemp)"
# 600

Never use /tmp/$$.<name> — PID-based names are predictable. An attacker can create symlinks in /tmp that your script will follow and overwrite. Use mktemp to get an unguessable name.

# BAD:  /tmp/mydata.$$
# GOOD: $(mktemp mydata.XXXXXX)

Avoid -u / --dry-run for real files — -u only prints a name; a TOCTOU race window exists between 'print' and 'create'. If you must use -u, understand the risk.

# Race condition:
name=$(mktemp -u)
# ... attacker creates $name as a symlink ...
echo data > "$name"   # follows symlink

Always quote the variable — Paths may contain spaces (especially on macOS where /var/folders/.../ is used). Unquoted expansion will break.

# GOOD:
rm -f "$tmp"
# BAD:
rm -f $tmp   # breaks on space

Clean up on early exit — Without a trap, aborted scripts leak files that may never be deleted (/tmp is cleared on reboot, /var/tmp is NOT).

# Without trap, Ctrl+C leaves $tmp behind:
tmp=$(mktemp) || exit 1
trap 'rm -f "$tmp"' EXIT

Mind filesystem boundaries for atomic mv — mv is only atomic when source and target are on the SAME filesystem. Place the temp file in the same directory as the target when doing atomic replacement.

# Same filesystem as target:
tmp=$(mktemp -p "$(dirname "$target")")

Conclusion

mktemp is a security tool, not just a convenience: it replaces the error-prone, predictable /tmp/$$ patterns with unguessable names, creates files atomically with mode 600, and thereby closes off symlink and TOCTOU attacks in shared directories. Remember three rules: always check the return value (|| exit 1), set a trap for cleanup immediately afterwards, and quote the variable on every use. Use -d for a directory; for cross-platform scripts, mind the differences between GNU and BSD (no -p/--suffix on macOS) and avoid -u/--dry-run for real files.

Further Reading

  • touch – create empty files and set timestamps
  • mkdir – create directories (also via mktemp -d)
  • mv – move files for atomic replacement