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>&1and1>&2operators. -
Non-blocking and timeout methods to wait on the process:
subprocessprovides timeout variants of its methods, such aswait_timeout(),join_timeout()andcapture_timeout(). -
Various conveniences, such as
checked()to flag non-zero exit status as error, thread-safe and cloneable process handle with&selfmethods, support forsetpgid()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 likejoin()andcapture()that start, collect results, and wait for the process to finish in one call. -
Job- interacts with a started process or pipeline, returned bystart(). It holds the pipe files (stdin,stdout,stderr) and has methods likecapture()for interacting with them. It contains a list ofProcesshandles and enables batch operations over processes likewait()andterminate().
Process- a cheaply cloneable handle to a single running process. It providespid(),wait(),poll(),terminate(), andkill(). Its methods take&self, so you can use them onProcessshared 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.