| Crates.io | subprocess |
| lib.rs | subprocess |
| version | 0.2.13 |
| created_at | 2017-01-14 23:04:56.21706+00 |
| updated_at | 2026-01-04 10:18:30.181488+00 |
| description | Execution of child processes and pipelines, inspired by Python's subprocess module, with Rust-specific extensions. |
| homepage | |
| repository | https://github.com/hniksic/rust-subprocess |
| max_upload_size | |
| id | 8066 |
| size | 245,333 |
The subprocess crate provides facilities for execution of and interaction with external
processes and pipelines. It is hosted on crates.io,
with API documentation on docs.rs.
The crate has minimal dependencies (only libc on Unix and winapi on Windows), and is
tested on Linux, macOS, and Windows.
The std::process module in the standard
library is fine for simple use cases, but it doesn't cover common scenarios such as:
Avoiding deadlock when communicating with a subprocess - if you need to write to a
subprocess's stdin while also reading its stdout and stderr, naive sequential operation can
block forever. subprocess handles this correctly using
poll-based I/O
multiplexing.
Shell-style pipelines - subprocess lets you create pipelines using the | operator:
Exec::cmd("find") | Exec::cmd("grep") | Exec::cmd("wc").
Merging stdout and stderr - shell-style 2>&1 redirection is directly supported with
Redirection::Merge,
which has no equivalent in std::process::Stdio.
Waiting with a timeout - std::process::Child offers either blocking wait() or
non-blocking try_wait(), but nothing in-between. subprocess provides
wait_timeout().
Sending signals (Unix) - std::process::Child::kill() only sends SIGKILL.
subprocess lets you send any
signal
including SIGTERM, and can signal process
groups
to terminate an entire process tree.
Preventing zombies - subprocess automatically waits on child processes when they go
out of scope (with
detach()
to opt out), whereas std::process::Child does not, risking zombie process accumulation.
| Need | std::process | subprocess |
|---|---|---|
| Wait with timeout | Loop with try_wait() + sleep |
wait_timeout(duration) |
| Write stdin while reading stdout | Manual threading or async | communicate() handles it |
| Pipelines | Manual pipe setup | cmd1 | cmd2 | cmd3 |
| Merge stderr into stdout | Not supported | Redirection::Merge |
| Send SIGTERM (Unix) | Only kill() (SIGKILL) |
send_signal(SIGTERM) |
| Signal process group (Unix) | Not supported | send_signal_group() |
| Auto-cleanup on drop | No (zombies possible) | Yes (waits by default) |
The API has two levels:
High-level: The
Exec builder provides a
convenient interface for spawning processes and pipelines, with methods like join(),
capture(), stream_stdout(), etc.
Low-level: The
Popen struct offers
direct control over the process lifecycle. Exec creates Popen instances which can then
be manipulated directly.
Execute a command and wait for it to complete:
let exit_status = Exec::cmd("umount").arg(dirname).join()?;
assert!(exit_status.success());
To prevent quoting issues and shell injection attacks, subprocess does not spawn a shell
unless explicitly requested. To execute a command through the OS shell, use Exec::shell:
Exec::shell("shutdown -h now").join()?;
Capture the output of a command:
let out = Exec::cmd("ls")
.stdout(Redirection::Pipe)
.capture()?
.stdout_str();
Capture both stdout and stderr merged together:
let out_and_err = Exec::cmd("ls")
.stdout(Redirection::Pipe)
.stderr(Redirection::Merge) // 2>&1
.capture()?
.stdout_str();
Provide input data and capture output:
let out = Exec::cmd("sort")
.stdin("b\nc\na\n")
.stdout(Redirection::Pipe)
.capture()?
.stdout_str();
assert_eq!(out, "a\nb\nc\n");
Get stdout as a Read trait object (like C's popen):
let stream = Exec::cmd("find").arg("/").stream_stdout()?;
// Use stream.read_to_string(), BufReader::new(stream).lines(), etc.
Create pipelines using the | operator:
let exit_status =
(Exec::shell("ls *.bak") | Exec::cmd("xargs").arg("rm")).join()?;
Capture the output of a pipeline:
let dir_checksum = {
Exec::shell("find . -type f") | Exec::cmd("sort") | Exec::cmd("sha1sum")
}.capture()?.stdout_str();
Give the process some time to run, then terminate if needed:
let mut p = Exec::cmd("sleep").arg("10").popen()?;
if let Some(status) = p.wait_timeout(Duration::from_secs(1))? {
println!("finished: {:?}", status);
} else {
println!("timed out, terminating");
p.terminate()?;
p.wait()?;
}
When you need to write to stdin and read from stdout/stderr simultaneously:
let mut p = Popen::create(&["cat"], PopenConfig {
stdin: Redirection::Pipe,
stdout: Redirection::Pipe,
..Default::default()
})?;
// communicate() handles the write/read interleaving to avoid deadlock
let (out, _err) = p.communicate(Some("hello world"))?;
assert_eq!(out.unwrap(), "hello world");
With a timeout:
let mut comm = Exec::cmd("slow-program")
.stdin("input")
.stdout(Redirection::Pipe)
.communicate()?
.limit_time(Duration::from_secs(5));
match comm.read_string() {
Ok((stdout, stderr)) => println!("got: {:?}", stdout),
Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {
println!("timed out, partial: {:?}", e.capture);
}
Err(e) => return Err(e.into()),
}
Send a signal other than SIGKILL:
use subprocess::unix::PopenExt;
let mut p = Exec::cmd("sleep").arg("100").popen()?;
p.send_signal(libc::SIGTERM)?; // graceful termination
p.wait()?;
Terminate an entire process tree using process groups:
use subprocess::unix::PopenExt;
// Start child in its own process group
let mut p = Popen::create(&["sh", "-c", "sleep 100 & sleep 100"], PopenConfig {
setpgid: true,
..Default::default()
})?;
// Signal the entire process group
p.send_signal_group(libc::SIGTERM)?;
p.wait()?;
For full control over the process lifecycle:
let mut p = Popen::create(&["command", "arg1", "arg2"], PopenConfig {
stdout: Redirection::Pipe,
..Default::default()
})?;
// Read stdout directly
let (out, err) = p.communicate(None)?;
// Check if still running
if let Some(exit_status) = p.poll() {
println!("finished: {:?}", exit_status);
} else {
println!("still running, terminating");
p.terminate()?;
}
subprocess is distributed under the terms of both the MIT license and the Apache License
(Version 2.0). See LICENSE-APACHE and LICENSE-MIT for
details. Contributing changes is assumed to signal agreement with these licensing terms.