>_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.
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.
| Flag | Argument | Behavior |
|---|---|---|
| -i, --input | INDEX | Write stdin into tempfile at INDEX |
| -o, --output | INDEX | Read tempfile at INDEX to stdout |
| -a, --add | INDEX [FILE] | Insert new tempfile at INDEX (from stdin or FILE) |
| -r, --remove | INDEX | Remove tempfile at INDEX |
| -p, --pop | Pop top of stack | |
| -u, --unshift | Push to bottom (stdin in, no stdout) | |
| -s, --shift | Shift from bottom | |
| -A, --append | INDEX | Append 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, --name | NAME | Tag new tempfile with NAME |
| -R, --rename | SRC NAME | Rename 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 | |
| --expire | HOURS | Purge tempfiles older than HOURS (fractional OK) |
| -e, --edit | INDEX | Open INDEX in $EDITOR (fallback vi) |
| -I, --info | INDEX | Show metadata for INDEX |
| --head | INDEX [LINES] | Head of INDEX (default 10) |
| --tail | INDEX [LINES] | Tail of INDEX (default 10) |
| --wc | INDEX | Line count of INDEX |
| --size | INDEX | Byte size of INDEX |
| --path | INDEX | Filesystem path of INDEX (for cat "$(tp --path 1)") |
| -g, --grep | PATTERN | Grep all tempfiles (exit 0 on match, 1 on none) |
| --replace | INDEX PAT REPL | Replace PAT with REPL in INDEX, print replacement count |
| -C, --cat | INDEX... | Concatenate listed tempfiles to stdout |
| -D, --diff | A B | Unified diff of two tempfiles |
| -M, --mv | FROM TO | Move tempfile from FROM to TO position |
| -S, --swap | A B | Swap two tempfiles |
| -x, --dup | INDEX | Duplicate INDEX onto top of stack |
| --rev | Reverse the entire stack | |
| --sort | name|size|mtime | Sort 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 locking —
fs2::FileExt::lock_exclusivewraps 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
| Variable | Effect |
|---|---|
| TEMPRS_DIR | Override 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. |
| EDITOR | Used 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— exits0if any tempfile matches,1if none. Use it as a guard:tp -g needle && echo found.tp -D a b— unified diff; exits0when the two tempfiles are identical,1when 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:
| Message | Cause |
|---|---|
| Invalid specified index | The 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 name | A tag (-w / -R) collides with an existing name. Names must be unique across the stack. |
| Name must not contain null bytes | Tag names are stored in the null-byte-delimited master record, so they cannot themselves contain \0. |
| No such file / Could not read file | A FILE argument does not exist or cannot be read. |
| Could not open editor | tp -e could not launch $EDITOR (or its vi fallback). |
| Could not acquire lock on master record | The flock on temprs-stack.lock could not be taken — usually a permissions issue on the temprs directory. |
| Could not write master record | The 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.