Execution of and interaction with external processes and pipelines
Rust
453
549 commits
updated Aug 5, 2026
The subprocess crate makes it convenient to execute and interact 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.
If you're upgrading from version 0.2, see the migration guide.
Compared to std::process, the crate
provides additional features:
OS-level pipelines using the | operator: Exec::cmd("find") | Exec::cmd("grep").arg(r"\.py$") | Exec::cmd("wc"). There is no difference between
interacting with pipelines and with a single process.
Capture and communicate family of methods for deadlock-free capturing of subprocess output/error, while simultaneously feeding data to its standard input. Capturing supports optional timeout and read size limit.
Flexible redirection options, such as connecting standard input to arbitrary data
sources, and merging output streams like shell's 2>&1 and 1>&2 operators.
Non-blocking and timeout methods to wait on the process: subprocess provides
timeout variants of its methods, such as
wait_timeout(),
join_timeout()
and
capture_timeout().
Various conveniences, such as checked() to flag non-zero exit status as error,
thread-safe and cloneable process handle with &self methods, support for setpgid()
on individual commands and on the pipeline, and many others.
The API consists of two main components:
Exec /
Pipeline -
builder-pattern API for configuring processes and pipelines. Once configured,
start()
starts the process or pipeline. Includes convenience methods like
join()
and
capture()
that start, collect results, and wait for the process to finish in one call.
Job - interacts
with a started process or pipeline, returned by
start().
It holds the pipe files (stdin, stdout, stderr) and has methods like capture()
for interacting with them. It contains a list of Process handles and enables batch
operations over processes like wait() and terminate().
Process - a
cheaply cloneable handle to a single running process. It provides pid(), wait(),
poll(), terminate(), and kill(). Its methods take &self, so you can use them on
Process shared across threads.Execute a command and wait for it to complete:
Exec::cmd("umount").arg(dirname).checked().join()?;
join() starts the command and waits for it to finish, returning the exit
status. checked() ensures an error is returned for non-zero exit status.
To prevent quoting issues and shell injection attacks, subprocess doesn't 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 stdout and stderr of a command, and use the stdout:
let rustver = Exec::shell("rustc --version").capture()?.stdout_str();
Capture stdout and stderr merged together:
let out_and_err = Exec::cmd("cargo").arg("check")
.stderr(Redirection::Merge) // 2>&1
.capture()?
.stdout_str();
capture() can simultaneously feed data to stdin and read stdout/stderr, avoiding the
deadlock that would result from doing these sequentially:
let lines = Exec::cmd("sqlite3")
.arg(db_path)
.stdin("SELECT name FROM users WHERE active = 1;")
.capture()?
.stdout_str();
Create pipelines using the | operator:
let top_mem = (Exec::cmd("ps").args(["aux"])
| Exec::cmd("sort").args(["-k4", "-rn"])
| Exec::cmd("head").arg("-5"))
.capture()?
.stdout_str();
Pipeline supports the same methods for interacting with the subprocess as with a single started command.
Get stdout as an object that implements std::io::Read (like C's popen):
let stream = Exec::cmd("find").arg("/").stream_stdout()?;
// Use stream.read_to_string(), BufReader::new(stream).lines(), etc.
stdin() doesn't accept just static strings, you can give it any owned data (such as in a
memory-mapped file or shared bytes::Bytes container), or generate it lazily:
use subprocess::InputData;
// send owned bytes
let bytes = bytes::Bytes::from("Hello world");
let gzipped_bytes = Exec::cmd("gzip")
.stdin(InputData::from_bytes(bytes))
.capture()?
.stdout;
// send a gigabyte of zeros
let lazy_source = std::io::repeat(0).take(1_000_000_000);
let gzipped_stream = Exec::cmd("gzip")
.stdin(InputData::from_reader(lazy_source))
.stream_stdout()?;
The data is streamed to the subprocess in chunks.
Capture with timeout:
let response = Exec::cmd("curl").arg("-s").arg(url)
.stdout(Redirection::Pipe)
.start()?
.capture_timeout(Duration::from_secs(10))?
.stdout_str();
communicate() can be used for more sophisticated control over timeouts, such as reading
with a time or size limit:
let mut comm = Exec::cmd("ping").arg("example.com").communicate()?;
let (out, _) = comm
.limit_time(Duration::from_secs(5))
.read_string()?;
Give the process some time to run, then terminate if needed:
let job = Exec::cmd("sleep").arg("10").start()?;
match job.wait_timeout(Duration::from_secs(1))? {
Some(status) => println!("finished: {:?}", status),
None => {
job.terminate()?;
job.wait()?;
}
}
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.
622 followers · starred Jan 2017
678 followers · starred Feb 2021
106 followers · starred Aug 2021
138 followers · starred Mar 2020
Execution of and interaction with external processes and pipelines
Rust
453
549 commits
updated Aug 5, 2026
The subprocess crate makes it convenient to execute and interact 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.
If you're upgrading from version 0.2, see the migration guide.
Compared to std::process, the crate
provides additional features:
OS-level pipelines using the | operator: Exec::cmd("find") | Exec::cmd("grep").arg(r"\.py$") | Exec::cmd("wc"). There is no difference between
interacting with pipelines and with a single process.
Capture and communicate family of methods for deadlock-free capturing of subprocess output/error, while simultaneously feeding data to its standard input. Capturing supports optional timeout and read size limit.
Flexible redirection options, such as connecting standard input to arbitrary data
sources, and merging output streams like shell's 2>&1 and 1>&2 operators.
Non-blocking and timeout methods to wait on the process: subprocess provides
timeout variants of its methods, such as
wait_timeout(),
join_timeout()
and
capture_timeout().
Various conveniences, such as checked() to flag non-zero exit status as error,
thread-safe and cloneable process handle with &self methods, support for setpgid()
on individual commands and on the pipeline, and many others.
The API consists of two main components:
Exec /
Pipeline -
builder-pattern API for configuring processes and pipelines. Once configured,
start()
starts the process or pipeline. Includes convenience methods like
join()
and
capture()
that start, collect results, and wait for the process to finish in one call.
Job - interacts
with a started process or pipeline, returned by
start().
It holds the pipe files (stdin, stdout, stderr) and has methods like capture()
for interacting with them. It contains a list of Process handles and enables batch
operations over processes like wait() and terminate().
Process - a
cheaply cloneable handle to a single running process. It provides pid(), wait(),
poll(), terminate(), and kill(). Its methods take &self, so you can use them on
Process shared across threads.Execute a command and wait for it to complete:
Exec::cmd("umount").arg(dirname).checked().join()?;
join() starts the command and waits for it to finish, returning the exit
status. checked() ensures an error is returned for non-zero exit status.
To prevent quoting issues and shell injection attacks, subprocess doesn't 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 stdout and stderr of a command, and use the stdout:
let rustver = Exec::shell("rustc --version").capture()?.stdout_str();
Capture stdout and stderr merged together:
let out_and_err = Exec::cmd("cargo").arg("check")
.stderr(Redirection::Merge) // 2>&1
.capture()?
.stdout_str();
capture() can simultaneously feed data to stdin and read stdout/stderr, avoiding the
deadlock that would result from doing these sequentially:
let lines = Exec::cmd("sqlite3")
.arg(db_path)
.stdin("SELECT name FROM users WHERE active = 1;")
.capture()?
.stdout_str();
Create pipelines using the | operator:
let top_mem = (Exec::cmd("ps").args(["aux"])
| Exec::cmd("sort").args(["-k4", "-rn"])
| Exec::cmd("head").arg("-5"))
.capture()?
.stdout_str();
Pipeline supports the same methods for interacting with the subprocess as with a single started command.
Get stdout as an object that implements std::io::Read (like C's popen):
let stream = Exec::cmd("find").arg("/").stream_stdout()?;
// Use stream.read_to_string(), BufReader::new(stream).lines(), etc.
stdin() doesn't accept just static strings, you can give it any owned data (such as in a
memory-mapped file or shared bytes::Bytes container), or generate it lazily:
use subprocess::InputData;
// send owned bytes
let bytes = bytes::Bytes::from("Hello world");
let gzipped_bytes = Exec::cmd("gzip")
.stdin(InputData::from_bytes(bytes))
.capture()?
.stdout;
// send a gigabyte of zeros
let lazy_source = std::io::repeat(0).take(1_000_000_000);
let gzipped_stream = Exec::cmd("gzip")
.stdin(InputData::from_reader(lazy_source))
.stream_stdout()?;
The data is streamed to the subprocess in chunks.
Capture with timeout:
let response = Exec::cmd("curl").arg("-s").arg(url)
.stdout(Redirection::Pipe)
.start()?
.capture_timeout(Duration::from_secs(10))?
.stdout_str();
communicate() can be used for more sophisticated control over timeouts, such as reading
with a time or size limit:
let mut comm = Exec::cmd("ping").arg("example.com").communicate()?;
let (out, _) = comm
.limit_time(Duration::from_secs(5))
.read_string()?;
Give the process some time to run, then terminate if needed:
let job = Exec::cmd("sleep").arg("10").start()?;
match job.wait_timeout(Duration::from_secs(1))? {
Some(status) => println!("finished: {:?}", status),
None => {
job.terminate()?;
job.wait()?;
}
}
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.
622 followers · starred Jan 2017
678 followers · starred Feb 2021
106 followers · starred Aug 2021
138 followers · starred Mar 2020