pub struct Command { /* private fields */ }Expand description
A description of a child process to launch: program, arguments, working directory, environment, stdin source, and an optional timeout.
A single builder for everything a run needs. Build it, then either drive it
to completion with a
helper (output_string, run, …) or
start it via a ProcessRunner for streaming/shared
groups.
Implementations§
Source§impl Command
impl Command
Sourcepub fn current_dir(self, dir: impl AsRef<Path>) -> Self
pub fn current_dir(self, dir: impl AsRef<Path>) -> Self
Set the working directory for the child process.
Relative-path programs and current_dir: if the program passed to
Command::new is a relative path (e.g. "./tool" or "../bin/x"),
it is resolved against the caller’s current directory at spawn time —
not against the directory set here. Use an absolute path for the program
when combining current_dir with a relative-path executable.
A bare-name program resolved via prefer_local
doesn’t share this footgun: a relative prefer_local directory is
always turned into an absolute path before being handed to the OS, so
it can’t be reinterpreted against the directory set here.
Sourcepub fn prefer_local(self, dir: impl Into<PathBuf>) -> Self
pub fn prefer_local(self, dir: impl Into<PathBuf>) -> Self
Probe dir for the program before the system PATH — for a
locally-installed tool (a project’s node_modules/.bin, target/debug,
a vendored toolchain) that a caller wants to run by bare name without
hand-rolling a PATH override.
Repeated calls accumulate, in priority order: the directory from the
first call is probed first, then the second, and so on, with the system
PATH tried last as the final fallback. Resolution reuses the exact
same PATHEXT-aware lookup as the PATH search (the same probe_dir
helper — no separate copy), so a .exe/.cmd/.bat on Windows is found
exactly as it would be on PATH.
Only affects a bare-name program. If the program passed to
Command::new is a path — absolute, or relative with a separator
("./tool", "../bin/x") — prefer_local has no effect and the
existing contract holds unchanged: such a program is never looked up
here or on PATH.
Does not touch the child’s own PATH. This only changes where the
parent looks to resolve the program for this one launch — the PATH
the child sees in its own environment (via inheritance, env,
or inherit_env) is neither rewritten nor extended.
When the program is found under one of these directories, the child is
simply spawned via that resolved absolute path instead of the bare name
(so the OS never has to search anything); a grandchild the program itself
spawns does not inherit this reach.
A relative dir here (e.g. "./node_modules/.bin") is probed against
the process’s actual current directory, not against whatever is set
via current_dir — and the resulting match is
always made absolute (by joining it onto that same current directory)
before being handed to the OS, so it can never be reinterpreted against
the child’s own working directory once current_dir
is also set.
If resolution fails everywhere, Error::NotFound’s
searched includes these directories — first, in priority order — ahead
of the PATH directories, so the diagnostic doesn’t hide that they were
checked too.
Sourcepub fn env(self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self
pub fn env(self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self
Set an environment variable for the child. To remove an inherited
variable, use env_remove — value here is always a
value, never None.
Secrets. env is the right channel for a token or password: env values
are redacted from this command’s Debug (only names appear) and are never
emitted via tracing or in cassette recordings, and the child receives the
value intact. Prefer it — or stdin, the strongest — over a
command-line arg: argv is reduced to a count in Debug too,
but is world-readable through the OS process table (/proc/<pid>/cmdline,
ps) and is exposed verbatim by command_line and
cassette recording. An env value is not world-readable, but is still
visible to the same user and root via /proc/<pid>/environ and is inherited
by every descendant process; stdin exposes the secret to neither.
processkit deliberately ships no Secret wrapper type — pair env with
the secrecy/zeroize crates for a typed, memory-scrubbed secret at
your own call sites and pass the exposed value here. Scrubbing only covers
your copy: once passed, processkit holds a plain OsString for the
command’s lifetime and the child receives cleartext (a core dump can expose
either). For a secret recomputed per operation — resolved when each command
is built and reused across that command’s retries, not regenerated per
attempt — use
CliClient::default_env_fn.
Sourcepub fn env_remove(self, key: impl AsRef<OsStr>) -> Self
pub fn env_remove(self, key: impl AsRef<OsStr>) -> Self
Remove an environment variable inherited from the parent.
Sourcepub fn envs<I, K, V>(self, vars: I) -> Self
pub fn envs<I, K, V>(self, vars: I) -> Self
Set multiple environment variables at once. Order is preserved; later entries win on a duplicated key.
use processkit::Command;
Command::new("tool").envs([("FOO", "1"), ("BAR", "2")]);Sourcepub fn env_clear(self) -> Self
pub fn env_clear(self) -> Self
Clear all inherited environment variables before applying any set here.
Opts out of client env defaults: a command that clears its environment
is treated as having taken full control of it, so a
CliClient’s default_env/
default_env_fn is not gap-filled
into it (a client default would otherwise pierce the clean slate). Set any
var you still want with an explicit env.
Sourcepub fn inherit_env<I, S>(self, names: I) -> Self
pub fn inherit_env<I, S>(self, names: I) -> Self
Inherit only the named variables from the parent environment —
an allow-list on top of an implied env_clear.
The named vars are copied from the parent environment at each spawn
(vars the parent lacks are skipped); explicit env /
env_remove overrides still apply afterwards.
Repeated calls extend the allow-list. Works on every platform.
A client default_env for an
allow-listed key is not applied — the command chose to inherit that
key from the parent, and a client default must not override it. A client
default for a key not in the list still fills (an explicit override
layered on top, orthogonal to parent inheritance) — so a client-wide safety
default reaches the command. Use env_clear instead to
opt out of client env defaults entirely.
Sourcepub fn uid(self, uid: u32) -> Self
pub fn uid(self, uid: u32) -> Self
Run the child as this user id (Unix privilege drop).
Applied by the OS between fork and exec; combine with
gid — the group id is set before the user id (once
the uid drops, changing gid is no longer permitted), an ordering the
standard library guarantees. On non-Unix targets the run fails with
Error::Unsupported — a requested
privilege drop is never silently skipped.
Linux cgroup caveat: under the cgroup v2 mechanism
(Mechanism::CgroupV2) the child joins
its cgroup after the OS has dropped the uid, by writing the
auto-created (and therefore not target-uid-writable) cgroup.procs —
so the spawn currently fails with a permission error rather than
producing an uncontained child. Privilege drop composes cleanly with
the POSIX process-group mechanism (macOS/BSD, or Linux without cgroup
delegation); making it compose with cgroups (e.g. chowning the cgroup
to the target uid) is tracked future work.
Sourcepub fn gid(self, gid: u32) -> Self
pub fn gid(self, gid: u32) -> Self
Run the child under this group id (Unix privilege drop) — see
uid for ordering and platform notes.
Sourcepub fn groups(self, gids: impl AsRef<[u32]>) -> Self
pub fn groups(self, gids: impl AsRef<[u32]>) -> Self
Set the child’s supplementary groups (Unix privilege drop), replacing the inherited set.
This is the missing third leg of a correct privilege drop: dropping the
uid/gid alone leaves the child holding the
parent’s supplementary groups (often root’s), so it could still reach
group-owned resources the target user shouldn’t. Pass the target user’s
groups (or [] to drop all extras) alongside uid/gid.
Ordering is handled for you: the OS applies setgroups → setgid →
setuid (groups and gid must be set while still privileged, before the
uid drops). On non-Unix targets the run fails with
Error::Unsupported — never silently
skipped. The Linux cgroup-v2 caveat from uid applies
unchanged.
Sourcepub fn setsid(self) -> Self
pub fn setsid(self) -> Self
Detach the child into a new session (Unix setsid()): no
controlling terminal, its own session and process group.
Containment is preserved: the group tracks the new session’s process
group (whose id is the child’s pid), so kill-on-drop and the teardown
verbs still reach it. On non-Unix targets the run fails with
Error::Unsupported.
Honored by the Command-driven launch paths (run/output_*/
start, ProcessGroup::start,
pipelines); the low-level raw-command
ProcessGroup::spawn escape hatch
bypasses these builders.
Sourcepub fn priority(self, priority: Priority) -> Self
pub fn priority(self, priority: Priority) -> Self
Launch this child at a lower (or higher) CPU-scheduling priority — for background/batch work that shouldn’t starve the foreground, or a task that should win over it.
Applied on both platforms via the existing spawn seams: Unix
setpriority in the same pre_exec hook that carries
uid/gid/setsid; Windows
a priority-class flag OR’d into creation_flags, the same seam as
create_no_window. Unlike the privilege
builders this never yields
Error::Unsupported — see
Priority for why both platforms cover every
variant, and the Unix caveat that lowering nice below its inherited
value — Priority::AboveNormal/
Priority::High always, and even
Priority::Normal under a positively-niced
parent — needs CAP_SYS_NICE/root there.
Last-write-wins with an earlier call, like timeout.
Sourcepub fn umask(self, mask: u32) -> Self
pub fn umask(self, mask: u32) -> Self
Set the file-mode creation mask for the child (Unix umask(2)),
controlling the default permissions of files it creates.
Applied via pre_exec, alongside setsid/
groups — another knob on that same seam. On
non-Unix targets the run fails with
Error::Unsupported rather than
silently ignoring the requested mask. Only the low permission bits are
meaningful (as with the umask(2) syscall itself); pass the value you
would give the umask shell builtin, e.g. 0o022.
Sourcepub fn kill_on_parent_death(self) -> Self
pub fn kill_on_parent_death(self) -> Self
Kill the direct child if this process dies abruptly — including
a SIGKILL of the parent, where Drop never runs to tear the group
down. An opt-in hardening on top of the unconditional kill-on-drop
containment, best-effort by design:
| Platform | Effect |
|---|---|
| Windows | Already guaranteed regardless of this knob: the kernel closes the Job Object handle when the parent dies, and kill-on-close takes the whole tree. Documented no-op. |
| Linux | prctl(PR_SET_PDEATHSIG, SIGKILL) on the direct child only — grandchildren are not covered (with the parent gone, nothing tears the cgroup/pgroup down). |
| macOS / BSD / other | No pdeathsig equivalent — does nothing (the graceful-exit guarantee via Drop still holds). |
Two honest Linux caveats:
- The death signal fires when the spawning thread dies, not only the
process — on a multi-threaded tokio runtime, a worker thread retired
while the child lives would kill it early (for the strongest guarantee
spawn from a current-thread runtime). The parent-died-before-arming race
is closed in the child by re-checking
getppid()against the spawner’s pid captured before the fork — safe in containers where the spawner itself is PID 1. - The kernel clears
PR_SET_PDEATHSIGacross anexecveof a set-uid / set-gid binary (a security measure), so this is silently void for asudo …/ setuid child — it inherits the pdeathsig for the tiny window beforeexecve, then loses it. Contain such a child with the kill-on-drop group (the default) rather than relying on this knob.
(Idea borrowed from execa’s cleanup-on-exit, mapped to native
primitives.)
Sourcepub fn create_no_window(self) -> Self
pub fn create_no_window(self) -> Self
Spawn without a console window (Windows CREATE_NO_WINDOW) — for a
GUI app launching a CLI tool without a flashing terminal.
On non-Windows targets this is a harmless no-op (purely cosmetic — no
console windows exist to suppress). Honored by the Command-driven
launch paths; the raw
ProcessGroup::spawn escape hatch still
overwrites creation flags (see its docs).
Sourcepub fn pipe(self, next: Command) -> Pipeline
pub fn pipe(self, next: Command) -> Pipeline
Chain this command’s stdout into next’s stdin — the first link of a
shell-free Pipeline. Keep chaining with
Pipeline::pipe (or the | operator), then
drive the whole thing with
Pipeline::output_string /
Pipeline::run.
Sourcepub fn unchecked_in_pipe(self) -> Self
pub fn unchecked_in_pipe(self) -> Self
Exempt this command, as a pipeline stage, from pipefail
attribution: its unclean exit (non-zero code, signal kill — including
SIGPIPE — or its own per-stage timeout kill) is
skipped when the chain decides what to report, and never shields a
checked stage’s failure. The motivating pattern is
producer | head -1: the consumer exits early, the producer dies of
SIGPIPE/EPIPE, and without this marker strict pipefail reports
that perfectly normal death as the chain’s failure. (Design borrowed
from duct’s unchecked() — the idea, not the code.)
Outside a Pipeline this is a no-op: a single
run’s status is already plain data in its
ProcessResult, and
ensure_success stays opt-in
— unchecked does not relax it, nor a whole-chain
Pipeline::timeout.
Sourcepub fn timeout(self, timeout: Duration) -> Self
pub fn timeout(self, timeout: Duration) -> Self
Kill the run if it exceeds timeout.
Clears a prior no_timeout — the last of the two wins.
Sourcepub fn no_timeout(self) -> Self
pub fn no_timeout(self) -> Self
Run without a timeout, and — unlike simply leaving the timeout unset —
opt out of any client-wide default_timeout
gap-fill. Use this to say “this one long-running command is deliberately
unbounded” against a client that otherwise imposes a deadline on every call
(a tail -f, a watch loop, an interactive session).
A plain Command (no client) is already unbounded by default, so this is
only meaningful when the command is run through a CliClient
with a default_timeout. Clears a prior timeout — the
last of the two wins.
Sourcepub fn timeout_opt(self, timeout: Option<Duration>) -> Self
pub fn timeout_opt(self, timeout: Option<Duration>) -> Self
Set the timeout from an optional Duration, folding the
timeout / no_timeout split into a
single composable verb for config-driven call sites. Some(d) is exactly
timeout(d); None is exactly
no_timeout().
Reach for it when you hold an Option<Duration> (a parsed config value, a
caller-supplied override) instead of the
match cfg { Some(d) => c.timeout(d), None => c.no_timeout() } dance. Mind
the None mapping: it means deliberately unbounded — opting out of a
client-wide default_timeout gap-fill,
not “leave the timeout unset for a default to fill”. Like the two verbs
it folds, it is last-write-wins with any earlier timeout call.
Sourcepub fn timeout_grace(self, grace: Duration) -> Self
pub fn timeout_grace(self, grace: Duration) -> Self
Make the timeout graceful: at the deadline the run’s
tree is sent SIGTERM (or the signal chosen via timeout_signal, with the
process-control feature), given up to grace to exit, then SIGKILLed.
Without it the deadline hard-kills at once. No effect unless
timeout is also set.
Windows has no signal tier: the deadline kills the job atomically
regardless of grace. Either way
timed_out stays true (the deadline
was exceeded), graceful or not.
Sourcepub fn timeout_signal(self, signal: Signal) -> Self
Available on crate feature process-control only.
pub fn timeout_signal(self, signal: Signal) -> Self
process-control only.The signal sent at the start of a graceful
timeout_grace window (default
Signal::Term). Unix-only in effect; ignored on
Windows (no signal tier).
This builder lives behind the process-control feature because the
Signal type does. Without process-control the
graceful timeout always uses SIGTERM (the default); the feature is only
needed to choose a different teardown signal — promoting Signal into
the base API would enlarge the always-on surface for a niche knob.
Sourcepub fn ok_codes(self, codes: impl IntoIterator<Item = i32>) -> Self
pub fn ok_codes(self, codes: impl IntoIterator<Item = i32>) -> Self
Treat these exit codes (not just 0) as success for the checking verbs —
run (and run_unit/checked via
ProcessRunnerExt) and
ProcessResult::ensure_success / is_success.
For tools whose non-zero exit is a normal result — grep (1 = no match),
diff (1 = differs), rsync’s code families — so callers don’t hand-match.
An empty set is ignored — a no-op that leaves the previously configured
codes (or the default [0]) in place, rather than resetting to [0], since
an empty accepted-set would make every exit a failure. Does not change
exit_code (always the raw code) or
probe (always the 0/1 convention).
Sourcepub fn cancel_on(self, token: CancellationToken) -> Self
pub fn cancel_on(self, token: CancellationToken) -> Self
Tie this run to token: cancelling it kills the process tree and makes
every consuming path (run/output_string/output_bytes/wait/
exit_code/probe/profile/finish and the streamed
finishers) resolve to Error::Cancelled.
In a Pipeline, a token on any stage cancels that
stage and the cancellation errors the whole pipeline (the private
pipeline group tears the other stages down).
Unlike timeout — which is captured in the
ProcessResult (timed_out) without erroring on the non-checking
paths — a cancellation is always an error, on every path. When both
fire, cancellation wins (it is checked first — except in first_line’s
narrow tie where the deadline watchdog closes the stream in the same poll
the token fires, which surfaces as Timeout). An already-cancelled token
short-circuits before spawning. On a private group the whole tree is
killed; on a shared group
(ProcessGroup::start) only the direct
child is, like timeout. Both wait_any and
first_line surface a mid-run cancel as
Err(Cancelled) — their streaming race resolves the cancellation and
tears the child down — as does an already-cancelled token via the
pre-spawn short-circuit. A mid-run cancel during
wait_for_line, by contrast,
closes the stream and surfaces as that probe’s
Error::NotReady, not Cancelled — the
consuming finisher afterwards still reports Cancelled.
A cancelled run is never retried: retry policies and
Supervisor restarts both treat
Error::Cancelled as terminal — the token stays cancelled forever, so
another attempt could only fail the same way.
On a Command this replaces any previously set token (last write
wins) — contrast the gap-fill containers
Pipeline::cancel_on and
CliClient::default_cancel_on,
which leave an explicit per-element token intact.
Sourcepub fn retry(
self,
max_attempts: u32,
backoff: Duration,
retry_if: impl Fn(&Error) -> bool + Send + Sync + 'static,
) -> Self
pub fn retry( self, max_attempts: u32, backoff: Duration, retry_if: impl Fn(&Error) -> bool + Send + Sync + 'static, ) -> Self
Retry the run while retry_if accepts the error, up to max_attempts
total attempts, sleeping a fixed backoff between tries. For exponential
backoff + cap + jitter, use retry_with.
Applies to the success-checking helpers —
run/run_unit/checked/exit_code/probe/parse/try_parse — on
Command, on ProcessRunnerExt,
and on CliClient: the ones that surface failure as an
Error the classifier can inspect (e.g. a transient network failure in
stderr, or Error::Timeout). The non-erroring
output_string/output_bytes paths don’t retry.
Each attempt re-executes the whole command — a fresh process. Only
retry operations that are safe to repeat: a side effect that already landed
before the failure (a git push that reached the server, then dropped the
connection) will be replayed. Prefer to gate retries on a classifier that
matches pre-effect failures (DNS/connection errors, Error::Timeout
while still connecting) rather than any non-zero exit.
A timeout bounds each attempt, not the whole retried
operation — there is no total wall-clock ceiling across retries (worst case
≈ attempts × timeout + the sum of the backoffs). Bound the total with
cancel_on (a Cancelled is terminal — never retried).
Because the command is replayed from scratch, a one-shot stdin source
(Stdin::from_reader /
from_lines) can’t survive a retry: its
payload is consumed by the first attempt and can’t be re-fed. So such a
command is not retried at all — the first attempt’s error is returned
as-is (retrying would either replay empty stdin or spuriously classify the
re-consume), and the retry policy is inert for it. Use a reusable source
(from_string/from_bytes/from_file/from_iter_lines) when retrying.
(A one-shot source re-run outside this retry loop — a Supervisor
incarnation, a pipeline re-run — does fail loud with
Error::Io InvalidInput at launch instead.)
Inert outside the success-checking verbs. A retry policy is
honored only by the verbs listed above. It is ignored by:
Supervisor— supervision is keep-alive restarting with its ownRestartPolicy/ backoff / storm handling, a different concern from replay-to-success; configure restarts there, not viaretry.output_all— a bounded fan-out that collects every outcome as data (no per-command retry); wrap each command’s verb yourself if a batch element must retry.- the raw
Pipelineverbs — a stage’sretrydoes not re-run that stage within the chain.
Counting: max_attempts is the total number of runs (so
retry(3, …) runs at most three times: the first plus two more).
max_attempts of 0 and 1 both mean a single run with no retry — a
command always runs at least once, so 0 does not mean “never run”. For
exponential backoff + cap + jitter instead of a fixed delay, use
retry_with, which takes a RetryPolicy — note that
a RetryPolicy counts max_retries (the runs after the first), so
retry(3, …) corresponds to RetryPolicy::new().max_retries(2).
Sourcepub fn retry_with(
self,
policy: RetryPolicy,
retry_if: impl Fn(&Error) -> bool + Send + Sync + 'static,
) -> Self
pub fn retry_with( self, policy: RetryPolicy, retry_if: impl Fn(&Error) -> bool + Send + Sync + 'static, ) -> Self
Retry on a rich RetryPolicy — exponential backoff + cap + jitter —
instead of the fixed (max_attempts, backoff) of retry.
The per-command analogue of
CliClient::default_retry, with the same
applicability and replay caveats as retry. Note
RetryPolicy counts max_retries (after the first attempt), whereas
retry counts max_attempts (total).
Sourcepub fn retry_never(self) -> Self
pub fn retry_never(self) -> Self
Opt out of retries entirely: run this command exactly once and
suppress any client-wide
default_retry gap-fill.
The explicit, symmetric counterpart to no_timeout:
a bare Command already retries nothing, so this is only meaningful
against a CliClient whose default_retry would
otherwise be filled in — it pins “run this one command once, whatever the
client policy”. Tidier than, and behaviorally identical to, the
retry(1, Duration::ZERO, |_| false) idiom (one attempt, a classifier that
accepts nothing). Last-write-wins with any earlier
retry / retry_with.
Sourcepub fn keep_stdin_open(self) -> Self
pub fn keep_stdin_open(self) -> Self
Leave stdin open after start so the child can be driven interactively via
RunningProcess::take_stdin.
Takes precedence over a stdin source — when set, that
source is ignored and the pipe is handed to the caller instead.
The open pipe lives until the caller takes it (take_stdin) or a
consuming verb runs: at consume time an untaken pipe is closed
(nothing could ever write to it again), so a stdin-reading child sees
EOF instead of blocking — combining keep_stdin_open with a bulk
helper (output_string, run, …) without ever taking the writer is
equivalent to not setting it. A writer the caller did take is
unaffected and keeps the pipe until dropped or
finished.
Mutually exclusive with inherit_stdin — a child
cannot both be handed an interactive stdin pipe and share the parent’s
stdin; setting both is rejected at launch (see inherit_stdin).
Sourcepub fn inherit_stdin(self) -> Self
pub fn inherit_stdin(self) -> Self
Give the child the parent’s own standard input — it reads directly from whatever this process’s stdin is connected to (a terminal, a file, a pipe) rather than from a crate-managed pipe.
This is the stdin counterpart of
stdout(StdioMode::Inherit) /
stderr(StdioMode::Inherit): the child shares the
parent stream instead of the crate mediating it. Reach for it when a
child must talk to the real terminal — git commit opening $EDITOR, a
tool prompting the user for a password or a yes/no, or simply forwarding
the parent’s piped stdin straight through to the child. Until a
pseudo-terminal exists (a future direction, not yet provided) this covers
the common non-tty-negotiating interactive cases without the crate having
to pump bytes.
Because the child reads the parent’s stdin directly, the crate neither
feeds nor captures that input, and there is no writer to
take_stdin (it returns None, as
for a non-keep_stdin_open run). stdout/stderr are unaffected — capture
and streaming of the child’s output keep working exactly as before.
§Mutually exclusive with a mediated stdin
Inheriting the parent’s stdin cannot be combined with either way the crate
would otherwise drive stdin — a configured stdin source
(Stdin::from_string/from_bytes/from_file/from_reader/from_lines,
or an explicit Stdin::empty()) or keep_stdin_open’s
interactive pipe. Setting inherit_stdin and one of those is a
contradiction (feed the child a source and let it read the terminal?),
so it is rejected at the launch boundary with a typed
Error::Io (InvalidInput) — the same failure mode
as the other stdin misconfiguration the crate refuses (re-running a
consumed one-shot source) — rather than silently letting one win. Drop the
other stdin knob to resolve it.
Sourcepub fn on_stdout_line<F>(self, handler: F) -> Self
pub fn on_stdout_line<F>(self, handler: F) -> Self
Invoke handler for each decoded stdout line as it is read (in addition
to capture/streaming). Runs on the pump task; keep it cheap. A handler
that panics is caught and disabled for the rest of the run — the
child is still drained and the result still carries every line (the
panic is reported as a tracing warn when that feature is on).
Ordering guarantees: invocations are FIFO within a stream; there
is no ordering between stdout and stderr handlers (two independent
pumps). On the consuming verbs (run/output_*/wait/profile/
finish) all handler invocations happen-before the awaited
future resolves — a progress bar can be finalized the moment the call
returns. (One documented exception: when a leaked pipe is held open
past the child’s death, teardown aborts the pump after a bounded
grace, cutting any not-yet-delivered lines along with their handler
calls.) On a streamed run, stdout handlers quiesce when the
stdout_lines stream ends.
At most one handler per stream: a repeat call replaces the previous one
(builder semantics, like timeout). To fan out, compose
inside a single closure.
Requires stdout to be Piped (the default):
the handler runs on the capture pump, so it never fires under
stdout(Inherit) / stdout(Null).
Byte cap caveat: a single line whose length exceeds a byte cap
(with_max_bytes) is never
assembled, so the handler never sees it either — it is silently skipped
for every sink (handler, tee, and capture buffer alike), counted only
via the truncation/dropped() signal. If every line matters, leave the
byte cap unset, or use a line cap instead.
Sourcepub fn on_stderr_line<F>(self, handler: F) -> Self
pub fn on_stderr_line<F>(self, handler: F) -> Self
Invoke handler for each decoded stderr line as it is read.
Same contract as on_stdout_line: runs on the
pump task, and a repeat call replaces the previous handler.
Sourcepub fn stdout(self, mode: StdioMode) -> Self
pub fn stdout(self, mode: StdioMode) -> Self
Set how the child’s standard output stream is connected (default:
StdioMode::Piped).
Piped(default) — captured into a pipe; all output-retrieval verbs (output_string,stdout_lines, …) read from it.Inherit— the child shares the parent’s stdout; output appears in the terminal/log but is not captured.Null— suppressed entirely (redirected to/dev/null).
With Inherit/Null there is no pipe to read, so the bulk capture
verbs (output_string/output_bytes) error rather than return
silently-empty output, and the streaming verbs (stdout_lines/
output_events) yield an empty stream. Use a discard verb (wait) to run
a command whose stdout you don’t want to capture.
Sourcepub fn stderr(self, mode: StdioMode) -> Self
pub fn stderr(self, mode: StdioMode) -> Self
Set how the child’s standard error stream is connected (default:
StdioMode::Piped).
Same semantics as stdout: Piped captures,
Inherit passes through, Null suppresses.
Sourcepub fn stdout_tee<W>(self, writer: W) -> Self
pub fn stdout_tee<W>(self, writer: W) -> Self
Tee every decoded stdout line to writer as it is produced — capture
and stream to writer simultaneously.
writer is an async sink (tokio::io::AsyncWrite); each decoded line
is written to it followed by \n. The write is awaited on the capture
pump, so a slow sink applies backpressure (the pump slows, the OS pipe
fills, the child blocks on its next write) rather than blocking the
runtime. The sink must make forward progress, though: a destination
that blocks forever (not merely slow) stalls the pump — no further
lines are buffered and a live stdout_lines/output_events consumer
parks — until the run’s teardown grace aborts the pump. A write error
disables the tee for the rest of the run — surfaced as a tracing warn
under the tracing feature, not silently swallowed — and capture is
unaffected.
Runs independently of on_stdout_line: set
both and both fire per line (the tee no longer replaces the handler).
A second stdout_tee replaces an earlier one.
Shared across clones and attempts. The sink is held in an
Arc<Mutex<…>>, so cloning the Command shares one sink — and a
Command is cloned for every Pipeline stage, every
Supervisor incarnation, and every
retry attempt. Concurrent clones (pipeline stages running
at once) interleave their lines into it; sequential re-runs (retries,
restarts) append — a retried command’s sink accumulates the failed
attempt’s output followed by the successful one’s, with no delimiter. For
per-run or per-attempt separation, tee to distinct sinks (a fresh Command
per run) or have the sink write its own delimiters.
The tee fires before the buffer policy decides retention, so it sees
every decoded line — including ones the capture buffer then drops or
rejects, e.g. output past a fail_loud
line ceiling (that ceiling bounds retained memory, not what streams past).
One exception: a single line whose length exceeds a byte cap
(with_max_bytes) is never
assembled, so it is neither retained nor teed — nor delivered to
on_stdout_line: the byte cap silently skips
that line for every sink alike, counted only via the
truncation/dropped() signal. Leave the byte cap unset (or use a line
cap) if every line must reach the tee. The discard verbs
(wait / profile) apply a large internal
in-flight byte cap for the same memory bound, so a line exceeding it is
likewise not teed under those verbs.
Requires stdout to be Piped (the default):
the tee fires from the capture pump, so it is a no-op under
stdout(Inherit) / stdout(Null), which
run no pump. It is likewise inert under
output_bytes, which captures stdout raw (no
line pump) — reach for a stdout tee with the line verbs (output_string,
start + stdout_lines, output_events).
Sourcepub fn stderr_tee<W>(self, writer: W) -> Self
pub fn stderr_tee<W>(self, writer: W) -> Self
Tee every decoded stderr line to writer as it is produced.
Same contract as stdout_tee — an async
tokio::io::AsyncWrite sink, awaited on the pump (backpressure, not
runtime-blocking), independent of on_stderr_line,
and requiring stderr to be Piped.
Sourcepub fn output_buffer(self, policy: OutputBufferPolicy) -> Self
pub fn output_buffer(self, policy: OutputBufferPolicy) -> Self
Cap the in-memory backlog of captured output lines (see
OutputBufferPolicy). The pump still drains the pipe; only retention is
bounded.
Sourcepub fn stdout_encoding(self, encoding: &'static Encoding) -> Self
pub fn stdout_encoding(self, encoding: &'static Encoding) -> Self
Decode stdout with encoding instead of UTF-8 (e.g.
encoding_rs::SHIFT_JIS).
Sourcepub fn stderr_encoding(self, encoding: &'static Encoding) -> Self
pub fn stderr_encoding(self, encoding: &'static Encoding) -> Self
Decode stderr with encoding instead of UTF-8.
Sourcepub fn encoding(self, encoding: &'static Encoding) -> Self
pub fn encoding(self, encoding: &'static Encoding) -> Self
Decode both stdout and stderr with encoding.
Sourcepub fn line_terminator(self, terminator: LineTerminator) -> Self
pub fn line_terminator(self, terminator: LineTerminator) -> Self
Choose where the line pump splits both streams into lines (see
LineTerminator). The default is LineTerminator::Newline — split on
\n only, unchanged from before this knob existed.
Pass LineTerminator::CarriageReturn to also treat a bare \r as a line
terminator, so carriage-return progress output (curl/pip/apt: a
bar redrawn in place with \r, no \n until the end) streams live, one
frame at a time instead of piling up as a single line that only surfaces
at EOF. In that mode each \r-delimited frame is a line for every line
sink alike — stdout_lines /
output_events, the
on_stdout_line/on_stderr_line
handlers, the stdout_tee/stderr_tee
sinks, and output_string — so there is a single, shared notion of a line.
A \r\n pair stays one terminator (no empty line between them), and the
OutputBufferPolicy byte cap now bounds an individual runaway frame
rather than dropping the whole stream.
Set it per stream with
stdout_line_terminator /
stderr_line_terminator when only one
stream carries progress output (progress usually lands on stderr, while
stdout stays newline-structured data).
Sourcepub fn stdout_line_terminator(self, terminator: LineTerminator) -> Self
pub fn stdout_line_terminator(self, terminator: LineTerminator) -> Self
Choose where the line pump splits stdout into lines (see
LineTerminator); the stderr framing is left untouched. See
line_terminator for both streams at once.
Sourcepub fn stderr_line_terminator(self, terminator: LineTerminator) -> Self
pub fn stderr_line_terminator(self, terminator: LineTerminator) -> Self
Choose where the line pump splits stderr into lines (see
LineTerminator); the stdout framing is left untouched. Handy when
progress output lands on stderr while stdout stays newline-structured.
Sourcepub fn command_line(&self) -> String
pub fn command_line(&self) -> String
Render this command as a single shell-quoted line for display — logs, error messages, a dry-run echo. Quoting is per-platform (POSIX single-quote / Windows double-quote) and is for readability, not execution: the crate never invokes a shell, and the rendering is not guaranteed to round-trip through one. Do not feed the output back to a shell to re-run the command — the escaping targets human legibility, not any specific shell’s parsing rules.
The line includes the arguments, which may carry secrets (a --token=…
flag). Unlike the tracing feature — which never logs argv — this is
opt-in: render it only into a sink you control.
Sourcepub fn working_dir(&self) -> Option<&Path>
pub fn working_dir(&self) -> Option<&Path>
The working-directory override, if one was set.
Sourcepub fn env_overrides(&self) -> &[(OsString, Option<OsString>)]
pub fn env_overrides(&self) -> &[(OsString, Option<OsString>)]
The environment overrides, in order (a None value removes the variable).
Sourcepub fn stdin_source(&self) -> Option<&Stdin>
pub fn stdin_source(&self) -> Option<&Stdin>
The configured stdin source, if any.
Sourcepub fn configured_timeout(&self) -> Option<Duration>
pub fn configured_timeout(&self) -> Option<Duration>
The configured deadline, if any — Some(d) for a
timeout(d), None for both an unset timeout and an
explicitly no_timeout (neither imposes a deadline).
Sourcepub async fn start(&self) -> Result<RunningProcess>
pub async fn start(&self) -> Result<RunningProcess>
Start the command and return a live RunningProcess backed by a fresh
private group. Use this for streaming stdout
(RunningProcess::stdout_lines) or inspecting the process while it
runs; keep the handle in scope, as dropping it tears the tree down.
§Errors
The launch surface shared by every run verb on Command:
Error::NotFound— the program could not be located (not installed, not onPATH, or the given path does not resolve to an executable).Error::Spawn— the program was located but the OS refused to start it (permission denied, a missing or non-directory working directory, a Windows.cmd/.batthat needscmd.exe, …).Error::Unsupported— a requested POSIX-only primitive (running as another user/group, a new session viasetsid, or aumask) is not available on this platform.Error::Cancelled— thecancel_ontoken was already cancelled before the spawn.Error::Io— the privateProcessGroupbacking the run could not be created, or a one-shot streaming stdin source (Stdin::from_reader/Stdin::from_lines) was already consumed by a previous run.Error::ResourceLimit— a resource cap configured on the run’s group could not be enforced.
Sourcepub async fn output_string(&self) -> Result<ProcessResult<String>>
pub async fn output_string(&self) -> Result<ProcessResult<String>>
Run to completion and capture stdout as text, stderr, and the exit code.
A non-zero exit is reported, not raised — call
ProcessResult::ensure_success to turn it into an error.
§Errors
The launch failures listed on start. A non-zero exit, a
timeout, and a signal-kill are captured in the returned
ProcessResult rather than raised (call
ensure_success to promote them);
beyond launch, only Error::Cancelled (a cancellation is always
raised), Error::OutputTooLarge (a fail-loud buffer overflowed),
Error::Stdin (a non-broken-pipe stdin failure on an
otherwise-successful run), and Error::Io surface.
Sourcepub async fn output_bytes(&self) -> Result<ProcessResult<Vec<u8>>>
pub async fn output_bytes(&self) -> Result<ProcessResult<Vec<u8>>>
Run to completion and capture stdout as raw bytes (plus stderr/exit code).
§Errors
Identical to output_string — a non-zero exit, a
timeout, or a signal-kill is captured in the ProcessResult, not raised
— except that a fail-loud Error::OutputTooLarge applies to the raw
stdout byte ceiling.
Sourcepub async fn exit_code(&self) -> Result<i32>
pub async fn exit_code(&self) -> Result<i32>
Run to completion and return just the exit code (output is discarded). A
run that yields no code surfaces as an error — a timeout as
Error::Timeout, a signal-kill as
Error::Signalled — consistent with
ProcessRunnerExt::exit_code and
CliClient::exit_code.
§Errors
The launch failures listed on start, plus — when the run
produced no code — Error::Timeout (the deadline elapsed),
Error::Signalled (killed by a signal), or Error::Cancelled. A
non-zero exit is returned as the code, not raised.
Sourcepub async fn run(&self) -> Result<String>
pub async fn run(&self) -> Result<String>
Run to completion, requiring an accepted exit (0 by default, widened
by ok_codes), and return trimmed stdout. Any other
code is Error::Exit.
§Errors
The launch failures listed on start, plus the
success-checking failures: Error::Exit (a non-accepted exit code),
Error::Signalled (a signal-kill), Error::Timeout (the deadline
elapsed — raised here, unlike on
output_string), Error::Cancelled,
Error::OutputTooLarge (a fail-loud buffer truncated the presented
stdout), and Error::Stdin (a non-broken-pipe stdin failure on an
otherwise-successful run).
Sourcepub async fn checked(&self) -> Result<ProcessResult<String>>
pub async fn checked(&self) -> Result<ProcessResult<String>>
Run to completion, require an accepted exit, and return the full
captured ProcessResult (untrimmed stdout) — the building block when you
need the whole result after success-checking rather than trimmed stdout
(run) or the raw result (output_string).
Consistent with ProcessRunnerExt::checked
and CliClient::checked.
§Errors
The same success-checking surface as run —
Error::Exit / Error::Signalled / Error::Timeout /
Error::Cancelled / Error::Stdin, atop the launch failures on
start — except that, as the lenient building block,
checked does not fail loud on a bounded-buffer truncation (inspect
ProcessResult::truncated yourself), so
it never returns Error::OutputTooLarge.
Sourcepub async fn run_unit(&self) -> Result<()>
pub async fn run_unit(&self) -> Result<()>
Run for the side effect: require an accepted exit (0, or any code in
ok_codes) and discard the output. Consistent with
ProcessRunnerExt::run_unit and
CliClient::run_unit.
§Errors
The same surface as checked (the launch failures on
start plus Error::Exit / Error::Signalled /
Error::Timeout / Error::Cancelled / Error::Stdin); only the
captured output is discarded.
Sourcepub async fn probe(&self) -> Result<bool>
pub async fn probe(&self) -> Result<bool>
Run a predicate command and read its exit code as a boolean: exit 0 →
Ok(true), exit 1 → Ok(false), anything else → Err (any other code
as Error::Exit, a timeout as Error::Timeout,
a signal-kill as Error::Signalled). For tools
whose exit code is the answer —
git diff --quiet, git show-ref --verify --quiet, grep -q, …
§Errors
Any exit code other than 0/1 becomes Error::Exit, and — atop the
launch failures on start — a run that produced no code
errors as Error::Timeout, Error::Signalled, or
Error::Cancelled. The strict 0/1 contract holds regardless of the
command’s ok_codes.
Sourcepub async fn parse<T, F>(&self, parse: F) -> Result<T>
pub async fn parse<T, F>(&self, parse: F) -> Result<T>
Run (requiring an accepted exit) and feed stdout to an infallible
parse closure, returning the parsed value. Fails loud on a bounded-buffer
truncation so the parser never sees a clipped tail. Consistent with
ProcessRunnerExt::parse and
CliClient::parse.
§Errors
The success-checking surface of run (the launch failures on
start, plus Error::Exit / Error::Signalled /
Error::Timeout / Error::Cancelled / Error::Stdin), plus
Error::OutputTooLarge when a fail-loud buffer truncated the stdout the
parser would see. The parse closure is infallible, so it adds no error.
Sourcepub async fn try_parse<T, F>(&self, parse: F) -> Result<T>
pub async fn try_parse<T, F>(&self, parse: F) -> Result<T>
Run (requiring an accepted exit) and feed stdout to a fallible
parse closure (the JSON-deserialization shape; a failure becomes
Error::Parse or whatever the closure returns).
Fails loud on truncation. Consistent with
ProcessRunnerExt::try_parse and
CliClient::try_parse.
§Errors
Everything parse can return, plus whatever the fallible
parse closure yields on malformed output — typically
Error::Parse.
Sourcepub async fn first_line<F>(&self, predicate: F) -> Result<Option<String>>
pub async fn first_line<F>(&self, predicate: F) -> Result<Option<String>>
Return the first stdout line matching predicate (or the first line when
the predicate is trivial), then tear the process down.
§Errors
The launch failures listed on start, plus
Error::Timeout when a timeout is set and its
deadline elapses mid-stream (which tears the process down),
Error::Cancelled, or Error::Io while streaming. A stream that ends
with no match is Ok(None), not an error.
Sourcepub fn resolve_program(&self) -> Result<PathBuf>
pub fn resolve_program(&self) -> Result<PathBuf>
Resolve this command’s program to a concrete executable path without
launching it — a spawn-free preflight, for a doctor / early-diagnosis
check (“is git installed?”) that must have no side effects. Unlike
probe (which actually runs the tool), this only locates
it — no process is ever started.
Resolution is byte-for-byte the same as the one the real launch performs,
because it reuses the same internal logic — not a second copy: a bare
name is resolved against this command’s prefer_local
directories first (in priority order), then the PATH, honoring PATHEXT on
Windows and the execute bit on Unix; a path-form program (absolute, or
relative with a separator) is probed directly, exactly as the OS receives
it. When the command has relocated the child’s PATH
(env/env_remove of PATH,
env_clear, or inherit_env),
the lookup runs against that effective child PATH, so preflight never
disagrees with what the spawn would actually find.
On success returns the resolved absolute path. This is a synchronous,
cheap filesystem probe (a few stats) — no async runtime is required.
§Errors
Error::NotFound when the program can’t be
located — not installed, not on PATH, or a path that doesn’t resolve to
an executable. Its searched field lists the directories that were
checked (prefer_local first, then PATH) for a bare-name lookup, and is
None for a path-form program; is_not_found
classifies it, exactly as it would for the same missing program on a real
run.
Trait Implementations§
Source§impl BitOr for Command
a | b — sugar for Command::pipe. Parenthesize the chain before a
terminal verb, since method calls bind tighter than |.
impl BitOr for Command
a | b — sugar for Command::pipe. Parenthesize the chain before a
terminal verb, since method calls bind tighter than |.
Source§impl BitOr<Command> for Pipeline
pipeline | c — sugar for Pipeline::pipe, so a | b | c chains
left-associatively into one pipeline.
impl BitOr<Command> for Pipeline
pipeline | c — sugar for Pipeline::pipe, so a | b | c chains
left-associatively into one pipeline.