rusty-pee
Fan stdin out to N concurrent shell-spawned children. Rust port of moreutils pee(1).
Run one stream through several commands at once: journalctl | rusty-pee 'grep selinux > a.log' 'wc -l > b.txt' feeds every byte to each child. It aggregates their exit codes, drops BrokenPipe sinks cleanly, & holds memory at O(BUFSIZ × N) instead of buffering the whole stream. Default mode adds --capture, --help, --version, & a completions subcommand; Strict mode mirrors moreutils' bitwise-OR exit aggregation & stderr layout. Prebuilt binaries ship for five targets (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64).
Part of the Rusty portfolio.
Install
# or, with prebuilt binaries:
# or, download directly from GitHub Releases:
# https://github.com/jsh562/rusty-pee/releases
To also install a pee binary alias (argv[0] auto-detect routes into Strict mode):
Usage
# Fan stdin out to multiple sinks that each consume the full stream
|
# Run two validators in parallel; aggregate their exit codes
|
# Capture mode: emit child stdouts in argv order after they exit
|
# Strict moreutils-compat mode (drop-in moreutils pee replacement)
|
RUSTY_PEE_STRICT=1 |
|
# Shell completions
Library API
The library fans out to N Box<dyn Write + Send> sinks instead of subprocesses. Use it when you want pee's broadcast pattern inside a long-running program without spawning a shell per consumer.
use ;
use Cursor;
let input = new;
let mut sink_a: = Vecnew;
let mut sink_b: = Vecnew;
let mut pee = new
.sink
.sink
.compat
.build?;
pee.run?;
assert_eq!;
assert_eq!;
# Ok::
For library-only consumers without CLI deps see the Cargo Features section.
Cargo Features
default enables full, which (for this single-capability port) resolves to the cli umbrella. pee-classic reproduces v0.1.x bare-port behavior matching upstream moreutils pee 1:1. To strip the CLI surface use default-features = false or --no-default-features & add the features you want.
rusty-pee is a single-capability port: its one documented job is "fan a single stdin stream out to N concurrent shell-spawned children, aggregate their exit codes, & surface failures cleanly". No optional feature leaves are carved beyond the required umbrellas; see docs/feature-layout.md for why.
Feature matrix
| Feature | Description | Umbrella(s) |
|---|---|---|
cli |
All CLI-only dependencies (clap, clap_complete, anyhow, signal-hook) and the binary entry point, signal-handler install, mode resolver, and Strict-mode pre-scanner. Library consumers strip via default-features = false. |
full, pee-classic, pee-minimal, pee-alias |
pee-alias |
Installs an additional pee binary alongside rusty-pee. Both share source; argv[0] auto-detect routes pee invocations into Strict mode. |
(standalone, implies cli) |
bench |
Pulls criterion and enables benches/throughput.rs. Dev-tooling only; outside the convention's leaf surface. Name preserved verbatim from v0.1.x. |
(standalone) |
dev-helpers |
Gates the fake-pee-child [[bin]] used by integration tests. Never installed by cargo install; enable with cargo test --features dev-helpers. Dev-tooling only. |
(standalone) |
Preset bundles
| Bundle | Composition | Use case |
|---|---|---|
pee-classic |
cli |
Drop-in upstream moreutils pee replacement. Strict mode is invoked via --strict, RUSTY_PEE_STRICT, or pee-alias argv[0] auto-detect. |
pee-minimal |
cli |
Explicit minimal-CLI alias for users who prefer the <port>-minimal naming convention seen across other portfolio ports. Identical composition to pee-classic. |
Keep-list workaround (Cargo features are union-only)
Cargo features cannot subtract from default. To get "everything except a specific feature," disable defaults & enumerate the features you want:
# → bare CLI with no pee-alias binary, no bench tooling.
# → CLI + the pee alias binary.
For the common cases the named preset bundles are usually sufficient.
Library-only consumers
[]
= { = "0.2", = false }
This strips clap, clap_complete, anyhow, & signal-hook. The resulting build pulls only thiserror & the target-conditional always-on deps (libc on Unix, windows-sys on Windows; both required for cross-platform child-process control regardless of feature selection).
Convention authority
This layout follows the portfolio-wide Cargo Features Convention. The "why" lives in ADR-0006; the "what" lives in project-instructions.md §Cargo Feature Surface. Every Rusty port from v0.2 onward exposes the same umbrella set (default / full / cli / <port>-classic), per-port leaves named in kebab-case, & 2 to 4 preset bundles.
Compatibility
rusty-pee has two modes:
- Default mode. clap-styled flag parser.
--capture,--help,--version, & thecompletionssubcommand are all available. Exit-code aggregation usesmax(child_codes). - Strict mode (activated by
--strict,RUSTY_PEE_STRICT=1, or invoking the binary aspee). Bitwise OR overWEXITSTATUSfor exit aggregation. moreutils-format stderr.--capture,--help,--version, &completionsMUST be rejected. Pinned upstream version: moreutils 0.69.
Pipeline-safety guarantee
Every live sink receives every byte the parent reads, in argv (CLI) / registration (library) order. When a child closes its stdin mid-stream (BrokenPipe), the parent drops it from the live-set & keeps feeding survivors. Memory MUST stay bounded at O(BUFSIZ × N), not O(input size).
Documented intentional divergences
- Exit-code aggregation. Default mode uses
max(child_codes); Strict mode preserves moreutils' bitwise OR overWEXITSTATUS. Example: children exiting 1 & 2 produce Default exit=2, Strict exit=3. - Hardcoded platform shell.
/bin/sh -c(Unix) /cmd /C(Windows). No--shell <path>flag in v0.1.0. - Unknown-flag stderr in Strict. Emits only the first unknown-flag error (
rusty-pee: invalid option -- 'X'). moreutils' POSIXGetopt::Longiterates per-character; we don't replicate that for undocumented inputs. --capture,--help,--version,completions. Default-mode additions; rejected in Strict.--no-ignore-sigpipe/--no-ignore-write-errors. moreutils exposes these to opt OUT of default-on behavior; rusty-pee v0.1.0 freezes the defaults on & provides no negation surface.- SIGTERM-to-SIGKILL grace period. Hardcoded 1 second (no flag, no env).
See docs/COMPATIBILITY.md for the full per-flag matrix.
pee-alias PATH-collision warning
Building with --features pee-alias installs a second binary named pee alongside rusty-pee. If moreutils is also on the same PATH, whichever directory comes first wins. Invoke rusty-pee (always unambiguous) or omit the pee-alias feature when moreutils is also present.
What's not shipped
--shell <path>override. Hardcoded/bin/sh -con Unix,cmd /Con Windows. Add a wrapper script if you need a different shell.--no-ignore-sigpipe/--no-ignore-write-errorsnegation flags. Defaults are frozen on; no negation surface in v0.1.0.- Configurable SIGTERM-to-SIGKILL grace period. Hardcoded 1 second.
- Source-code derivation from moreutils. This is a clean-room reimplementation. The moreutils
peePerl source is GPL'd & untouched. Same posture asuutils/coreutils.
MSRV
Rust 1.85 (edition 2024). Re-verified against the portfolio's stable-minus-two policy at each release.
License
Dual-licensed under MIT or Apache-2.0 at your option.