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.
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.
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.
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.
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 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). |
One honest Linux caveat: 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.
(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_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 (it would make every exit a failure); the default
stays [0]. 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). 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 child
is, exactly like timeout. wait_any and
first_line don’t synthesize the error for a
mid-run cancel — their stream simply ends, mirroring how they treat
timeout — though a token that was already cancelled still surfaces
the pre-spawn Err(Cancelled) short-circuit. Likewise a mid-run cancel
during wait_for_line 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 backoff between tries.
Applies to the success-checking helpers — run,
exit_code, probe, and the
CliClient run/run_unit/exit_code/parse/try_parse
helpers — i.e. 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.
Because the command is replayed from scratch, a one-shot stdin source
(Stdin::from_reader /
from_lines) won’t survive a retry: its
payload is consumed by the first attempt. Rather than silently feed the
retry empty stdin, the second attempt fails loud with an
Error::Io (InvalidInput) naming the consumed
source. Use a reusable source
(from_string/from_bytes/from_file/from_iter_lines) when retrying.
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.
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.
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).
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.
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
ceiling (that ceiling bounds retained memory, not what streams past).
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 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 timeout, if any.
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.
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.
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).
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.
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.
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.
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.
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, …
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.
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.
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.