Skip to content

Execution of and interaction with external processes and pipelines

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

hniksic/rust-subprocess

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

489 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

subprocess

crates.io CI docs.rs

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.

Why subprocess?

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.

API Overview

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.

Examples

Execution

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()?;

Capturing output

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();

Pipelines

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.

Streaming

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.

Arbitrary input

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.

Timeout

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()?;

Termination

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()?;
    }
}

License

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.

About

Execution of and interaction with external processes and pipelines

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published

Contributors 19

Languages