// TEMPRS — TEMPORARY FILE STACK MANAGER

temprs v2.9.12 · Two binaries (tp + temprs) · Dual indexing (position + @name) · Null-byte master record · flock-protected, atomic writes · 5,644 tests

4,767 production Rust lines · 57,945 test Rust lines · 290 commits · 5 direct deps · zsh completion + two man pages

Report GitHub Issues
// Color scheme

>_TEMPRS — PUSH YOUR DATA. OWN YOUR TEMP FILES.

A stack-based temporary file manager for the shell. Pipe data into tp, get a numbered tempfile on top of a persistent stack. Address files by position (1, -1) or by tag (@mydata). Stack operations mirror Perl/Ruby arrays: push, pop, shift, unshift, move, swap, duplicate, reverse, sort. The master record is null-byte delimited, atomically renamed, and flock-protected so concurrent shells can't corrupt it. Both tp and temprs binaries are shipped from one Cargo target.

Quickstart

Install from crates.io or build from source. tp is the short alias; temprs is the long name. Both binaries are identical.

# install
cargo install temprs

# from source
git clone https://github.com/MenkeTechnologies/temprs
cd temprs && cargo build --release

# push stdin to a new tempfile on top of stack
echo "hello" | tp

# list the stack
tp -l

# read top of stack to stdout
tp | wc -l

# read tempfile at index 1
tp -o 1 | head -5

# tag a tempfile so you can recall it by name
ps aux | tp -w procs
tp -o procs | grep firefox

Full install + usage live in the README; two man pages are shipped under man/man1/ — a short reference (temprs.1, man temprs) and a full manual (temprsall.1, man temprsall).


Stack architecture

Tempfiles are ordered in a persistent stack. Top of stack is the newest entry. Indices are 1-based ascending from the bottom; negative indices count from the top (-1 = top, -stack_size = bottom). Index 0 is always invalid.

┌─────────────────────────────────────┐ │ INDEX N ▓▓ TOP OF STACK (newest)│ │ INDEX N-1 ▓▓ ... │ │ INDEX 2 ▓▓ ... │ │ INDEX 1 ▓▓ BOTTOM OF STACK │ └─────────────────────────────────────┘

Files live in $TMPDIR/temprs/ (override with TEMPRS_DIR). The master record (temprs-stack) maps each stack position to its filename and optional tag; the individual payloads are tempfile<timestamp> files alongside it. Writes go through temprs-stack.tmp and are atomically renamed, with a temprs-stack.lock guarding concurrent access. Stack position is reconstructed from the master on every invocation, so multiple shells share one canonical view.


Dual indexing — by position or by @name

Every operation that takes an INDEX also accepts a tag name. Resolution order: numeric parse first, then tag lookup. Names are unique across the stack and travel with their files through every stack mutation (move, swap, duplicate). Names render with a @ prefix in listings.

# tag on creation
ps aux | tp -w procs

# tag on existing file (rename)
tp -R 1 newname
tp -R procs procs2

# use the tag anywhere INDEX is accepted
tp -o procs
tp -r procs
tp -A procs < more.txt
tp -M procs 1
tp -S procs other
tp -x procs
tp --head procs 10
tp --replace procs old new

Operations — full surface

I/O

tp (push stdin), tp FILE (load file), tp -i N (write into N), tp -o N (read N to stdout), tp -v (verbose = also write to stdout), tp -q (quiet = suppress create output), tp -A N (append to N).

Stack mutation

-p pop, -u unshift, -s shift, -a N insert at N, -r N remove, -M src dst move, -S a b swap, -x N duplicate to top.

Bulk

--rev reverse the stack, --sort name|size|mtime sort, -c purge all, --expire HOURS purge by mtime (fractional hours OK: 0.5).

Listing

-l list, -L list with contents, -n numbered, -N numbered with contents, -k count, -d show temprs directory, -m show master file.

Inspect

-I N metadata, --head N [LINES], --tail N [LINES], --wc N, --size N, --path N (raw filesystem path for shell substitution).

Search & transform

-g PATTERN grep across all tempfiles (exit 0/1), --replace N PAT REPL find-and-replace in place (prints replacement count).

Combine

-C a b c concat tempfiles to stdout in order (indices and names freely mixed), -D a b unified diff of two tempfiles.

Tagging

-w NAME tag a new tempfile on creation, -R src NAME rename a tag, names render as @name in listings.

Editor

-e N open in $EDITOR (falls back to vi), works with -1 for top-of-stack.


Flag reference

The same flag set is bound under both binaries. Long forms shown where they exist; tp -h prints the cyberpunk banner + grouped help.

FlagArgumentBehavior
-i, --inputINDEXWrite stdin into tempfile at INDEX
-o, --outputINDEXRead tempfile at INDEX to stdout
-a, --addINDEX [FILE]Insert new tempfile at INDEX (from stdin or FILE)
-r, --removeINDEXRemove tempfile at INDEX
-p, --pop Pop top of stack
-u, --unshift Push to bottom (stdin in, no stdout)
-s, --shift Shift from bottom
-A, --appendINDEXAppend stdin to tempfile at INDEX
-v, --verbose Also echo data to stdout on write (repeatable; raises verbosity level)
-q, --quiet Suppress output when creating a tempfile
-w, --nameNAMETag new tempfile with NAME
-R, --renameSRC NAMERename tag (SRC = index or current name)
-l, --list-files List stack
-L, --list-contents List stack with contents
-n, --list-files-numbered List stack numbered
-N, --list-contents-numbered List stack numbered with contents
-k, --count Print stack size
-d, --dir Print temprs directory
-m, --master Print master record path
-c, --clear Purge all tempfiles
--expireHOURSPurge tempfiles older than HOURS (fractional OK)
-e, --editINDEXOpen INDEX in $EDITOR (fallback vi)
-I, --infoINDEXShow metadata for INDEX
--headINDEX [LINES]Head of INDEX (default 10)
--tailINDEX [LINES]Tail of INDEX (default 10)
--wcINDEXLine count of INDEX
--sizeINDEXByte size of INDEX
--pathINDEXFilesystem path of INDEX (for cat "$(tp --path 1)")
-g, --grepPATTERNGrep all tempfiles (exit 0 on match, 1 on none)
--replaceINDEX PAT REPLReplace PAT with REPL in INDEX, print replacement count
-C, --catINDEX...Concatenate listed tempfiles to stdout
-D, --diffA BUnified diff of two tempfiles
-M, --mvFROM TOMove tempfile from FROM to TO position
-S, --swapA BSwap two tempfiles
-x, --dupINDEXDuplicate INDEX onto top of stack
--rev Reverse the entire stack
--sortname|size|mtimeSort the stack

Data integrity

The master record is the single source of truth for the stack. Three layered protections prevent corruption under concurrent access:

  • Null-byte delimited format — fields separated by \0, records by \0\0. Filenames containing newlines, tabs, spaces, or any other shell-metacharacters parse correctly. No quoting, no escaping.
  • Atomic writes — the master is written to a sibling temp file and renamed into place. A crash mid-write leaves the previous master intact; readers never see a partial record.
  • Exclusive file lockingfs2::FileExt::lock_exclusive wraps every read-modify-write. Two shells racing on the same stack serialize cleanly; neither sees a half-mutated state.
  • Auto-recovery — corrupt or empty records (e.g. from an aborted external edit) are silently skipped on read and cleaned up on the next write.

Environment

VariableEffect
TEMPRS_DIROverride the tempfile + master record directory. Default: $TMPDIR/temprs (or /tmp/temprs if $TMPDIR is unset). The directory is created on first use. tp -c only purges when the directory basename contains temprs, so keep that substring in any custom path.
EDITORUsed by tp -e INDEX. Falls back to vi.

Shell integration

A zsh completion lives at completions/_temprs and dynamically resolves stack indices, file names, and @name tags — tab-completion sees the current stack, not a static word list.

# install into your fpath
cp completions/_temprs /usr/local/share/zsh/site-functions/_tp

# or extend fpath in .zshrc
fpath=(/path/to/temprs/completions $fpath)
autoload -Uz compinit && compinit

Two groff man pages are shipped under man/man1/temprs.1 (short reference) and temprsall.1 (full manual). Install to a directory on $MANPATH for man temprs and man temprsall.


Pipeline examples

# capture, transform, recall
ps aux | tp -w procs
tp -o procs | awk '$3 > 5'

# diff two captures
curl -s api/v1/state | tp -w before
make deploy
curl -s api/v1/state | tp -w after
tp -D before after

# build a working set, then dump it
grep -r TODO src | tp -w todos
grep -r FIXME src | tp -w fixmes
tp -C todos fixmes | sort -u

# edit a captured payload, then re-emit
curl -s config.json | tp -w cfg
tp -e cfg
tp -o cfg | curl -X PUT --data-binary @- api/v1/config

# expiry & housekeeping
tp --expire 24    # drop anything older than a day
tp --expire 0.5   # drop anything older than 30 minutes
tp -c             # drop everything

Exit codes & scripting

A few operations are designed to be used in shell conditionals — they set the exit status, not just print output:

  • tp -g PATTERN — exits 0 if any tempfile matches, 1 if none. Use it as a guard: tp -g needle && echo found.
  • tp -D a b — unified diff; exits 0 when the two tempfiles are identical, 1 when they differ.
  • tp --replace N PAT REPL — performs the in-place edit and prints the number of replacements made, so a script can branch on whether anything changed.
  • tp --path N — prints the raw filesystem path with no decoration, intended for command substitution: cat "$(tp --path 1)".

Troubleshooting

Errors are written to stderr in a terse form. The messages map directly to the constants in src/util/consts.rs:

MessageCause
Invalid specified indexThe index is out of range (0, or beyond ±stack_size), or a @name that does not resolve. Positive range is 1..stack_size, negative range is -stack_size..-1; index 0 is never valid.
Invalid or duplicate nameA tag (-w / -R) collides with an existing name. Names must be unique across the stack.
Name must not contain null bytesTag names are stored in the null-byte-delimited master record, so they cannot themselves contain \0.
No such file / Could not read fileA FILE argument does not exist or cannot be read.
Could not open editortp -e could not launch $EDITOR (or its vi fallback).
Could not acquire lock on master recordThe flock on temprs-stack.lock could not be taken — usually a permissions issue on the temprs directory.
Could not write master recordThe atomic write of the master record failed (disk full, permissions).

Custom directory safety. tp -c (purge all) refuses to operate unless the basename of the temprs directory contains the string temprs. This guards against an accidental TEMPRS_DIR=$HOME wiping unrelated files. Keep temprs in any custom directory name.

If the stack ever looks inconsistent, inspect the raw store: tp -d prints the temprs directory and tp -m prints the master record path. Empty or malformed records are skipped on read and cleaned up on the next write, so a single bad record self-heals.


Development & CI

The repository ships with a GitHub Actions workflow at .github/workflows/ci.yml that runs cargo check, the full cargo test suite (5,644 tests — 5,059 unit + 585 integration), cargo fmt --check, cargo clippy, cargo doc, and a release build on every push to main and on every pull request. Criterion benchmarks live under benches/benchmarks.rs (cargo bench).

For more detail on architecture, test breakdown, and module-level statistics, see the engineering report.