Struct duct::Expression

source ·
pub struct Expression(/* private fields */);
Expand description

The central objects in Duct, Expressions are created with cmd or cmd!, combined with pipe, and finally executed with run, read, start, or reader. They also support several methods to control their execution, like stdin_bytes, stdout_capture, env, and unchecked.

Expressions are immutable, and they do a lot of Arc sharing internally, so all of the methods below take &self and return a new Expression cheaply.

Expressions using pipe form trees, and the order in which you call different methods can matter, just like it matters where you put redirections in Bash. For example, each of these expressions suppresses output differently:

// Only suppress stderr on the left side.
cmd!("foo").stderr_null().pipe(cmd!("bar")).run()?;

// Only suppress stderr on the right side.
cmd!("foo").pipe(cmd!("bar").stderr_null()).run()?;

// Suppress stderr on both sides.
cmd!("foo").pipe(cmd!("bar")).stderr_null().run()?;

Implementations§

source§

impl Expression

source

pub fn run(&self) -> Result<Output>

Execute an expression, wait for it to complete, and return a std::process::Output object containing the results. Nothing is captured by default, but if you build the expression with stdout_capture or stderr_capture then the Output will hold those captured bytes.

Errors

In addition to all the IO errors possible with std::process::Command, run will return an ErrorKind::Other IO error if child returns a non-zero exit status. To suppress this error and return an Output even when the exit status is non-zero, use the unchecked method.

Example
let output = cmd!("echo", "hi").stdout_capture().run().unwrap();
assert_eq!(b"hi\n".to_vec(), output.stdout);
source

pub fn read(&self) -> Result<String>

Execute an expression, capture its standard output, and return the captured output as a String. This is a convenience wrapper around reader. Like backticks and $() in the shell, read trims trailing newlines.

Errors

In addition to all the errors possible with run, read will return an error if the captured bytes aren’t valid UTF-8.

Example
let output = cmd!("echo", "hi").stdout_capture().read().unwrap();
assert_eq!("hi", output);
source

pub fn start(&self) -> Result<Handle>

Start running an expression, and immediately return a Handle that represents all the child processes. This is analogous to the spawn method in the standard library. The Handle may be shared between multiple threads.

Example
let handle = cmd!("echo", "hi").stdout_capture().start().unwrap();
let output = handle.wait().unwrap();
assert_eq!(b"hi\n".to_vec(), output.stdout);
source

pub fn reader(&self) -> Result<ReaderHandle>

Start running an expression, and immediately return a ReaderHandle attached to the child’s stdout. This is similar to .stdout_capture().start(), but it returns the reader to the caller rather than reading from a background thread.

Note that because this method doesn’t read child output on a background thread, it’s a best practice to only create one ReaderHandle at a time. Child processes with a lot of output will eventually block if their stdout pipe isn’t read from. If you have multiple children running, but you’re only reading from one of them at a time, that could block the others and lead to performance issues or deadlocks. For reading from multiple children at once, prefer .stdout_capture().start().

Example
let mut reader = cmd!("echo", "hi").reader().unwrap();
let mut stdout = Vec::new();
reader.read_to_end(&mut stdout).unwrap();
assert_eq!(b"hi\n".to_vec(), stdout);
source

pub fn pipe<T: Into<Expression>>(&self, right: T) -> Expression

Join two expressions into a pipe expression, where the standard output of the left will be hooked up to the standard input of the right, like | in the shell.

Errors

During execution, if one side of the pipe returns a non-zero exit status, that becomes the status of the whole pipe, similar to Bash’s pipefail option. If both sides return non-zero, and one of them is unchecked, then the checked side wins. Otherwise the right side wins.

During spawning, if the left side of the pipe spawns successfully, but the right side fails to spawn, the left side will be killed and awaited. That’s necessary to return the spawn error immediately, without leaking the left side as a zombie.

Example
let output = cmd!("echo", "hi").pipe(cmd!("sed", "s/h/p/")).read();
assert_eq!("pi", output.unwrap());
source

pub fn stdin_bytes<T: Into<Vec<u8>>>(&self, bytes: T) -> Expression

Use bytes or a string as input for an expression, like <<< in the shell. A worker thread will write the input at runtime.

Example
// Many types implement Into<Vec<u8>>. Here's a string.
let output = cmd!("cat").stdin_bytes("foo").read().unwrap();
assert_eq!("foo", output);

// And here's a byte slice.
let output = cmd!("cat").stdin_bytes(&b"foo"[..]).read().unwrap();
assert_eq!("foo", output);
source

pub fn stdin_path<T: Into<PathBuf>>(&self, path: T) -> Expression

Open a file at the given path and use it as input for an expression, like < in the shell.

Example
// Many types implement Into<PathBuf>, including &str.
let output = cmd!("head", "-c", "3").stdin_path("/dev/zero").read().unwrap();
assert_eq!("\0\0\0", output);
source

pub fn stdin_file<T: IntoRawFd>(&self, file: T) -> Expression

Use an already opened file or pipe as input for an expression.

Example
let input_file = std::fs::File::open("/dev/zero").unwrap();
let output = cmd!("head", "-c", "3").stdin_file(input_file).read().unwrap();
assert_eq!("\0\0\0", output);
source

pub fn stdin_null(&self) -> Expression

Use /dev/null (or NUL on Windows) as input for an expression.

Example
let output = cmd!("cat").stdin_null().read().unwrap();
assert_eq!("", output);
source

pub fn stdout_path<T: Into<PathBuf>>(&self, path: T) -> Expression

Open a file at the given path and use it as output for an expression, like > in the shell.

Example
// Many types implement Into<PathBuf>, including &str.
let path = cmd!("mktemp").read().unwrap();
cmd!("echo", "wee").stdout_path(&path).run().unwrap();
let mut output = String::new();
std::fs::File::open(&path).unwrap().read_to_string(&mut output).unwrap();
assert_eq!("wee\n", output);
source

pub fn stdout_file<T: IntoRawFd>(&self, file: T) -> Expression

Use an already opened file or pipe as output for an expression.

Example
let path = cmd!("mktemp").read().unwrap();
let file = std::fs::File::create(&path).unwrap();
cmd!("echo", "wee").stdout_file(file).run().unwrap();
let mut output = String::new();
std::fs::File::open(&path).unwrap().read_to_string(&mut output).unwrap();
assert_eq!("wee\n", output);
source

pub fn stdout_null(&self) -> Expression

Use /dev/null (or NUL on Windows) as output for an expression.

Example
// This echo command won't print anything.
cmd!("echo", "foo", "bar", "baz").stdout_null().run().unwrap();

// And you won't get anything even if you try to read its output! The
// null redirect happens farther down in the expression tree than the
// implicit `stdout_capture`, and so it takes precedence.
let output = cmd!("echo", "foo", "bar", "baz").stdout_null().read().unwrap();
assert_eq!("", output);
source

pub fn stdout_capture(&self) -> Expression

Capture the standard output of an expression. The captured bytes will be available on the stdout field of the std::process::Output object returned by run or wait. Output is read by a background thread, so the child will never block writing to stdout. But note that read and reader can be more convenient, and they don’t require the background thread.

Example
// The most direct way to read stdout bytes is `stdout_capture`.
let output1 = cmd!("echo", "foo").stdout_capture().run().unwrap().stdout;
assert_eq!(&b"foo\n"[..], &output1[..]);

// The `read` method is a shorthand for `stdout_capture`, and it also
// does string parsing and newline trimming.
let output2 = cmd!("echo", "foo").read().unwrap();
assert_eq!("foo", output2)
source

pub fn stdout_to_stderr(&self) -> Expression

Join the standard output of an expression to its standard error pipe, similar to 1>&2 in the shell.

Example
let output = cmd!("echo", "foo").stdout_to_stderr().stderr_capture().run().unwrap();
assert_eq!(&b"foo\n"[..], &output.stderr[..]);
source

pub fn stderr_path<T: Into<PathBuf>>(&self, path: T) -> Expression

Open a file at the given path and use it as error output for an expression, like 2> in the shell.

Example
// Many types implement Into<PathBuf>, including &str.
let path = cmd!("mktemp").read().unwrap();
cmd!("sh", "-c", "echo wee >&2").stderr_path(&path).run().unwrap();
let mut error_output = String::new();
std::fs::File::open(&path).unwrap().read_to_string(&mut error_output).unwrap();
assert_eq!("wee\n", error_output);
source

pub fn stderr_file<T: IntoRawFd>(&self, file: T) -> Expression

Use an already opened file or pipe as error output for an expression.

Example
let path = cmd!("mktemp").read().unwrap();
let file = std::fs::File::create(&path).unwrap();
cmd!("sh", "-c", "echo wee >&2").stderr_file(file).run().unwrap();
let mut error_output = String::new();
std::fs::File::open(&path).unwrap().read_to_string(&mut error_output).unwrap();
assert_eq!("wee\n", error_output);
source

pub fn stderr_null(&self) -> Expression

Use /dev/null (or NUL on Windows) as error output for an expression.

Example
// This echo-to-stderr command won't print anything.
cmd!("sh", "-c", "echo foo bar baz >&2").stderr_null().run().unwrap();
source

pub fn stderr_capture(&self) -> Expression

Capture the error output of an expression. The captured bytes will be available on the stderr field of the Output object returned by run or wait. Output is read by a background thread, so the child will never block writing to stderr.

Example
let output_obj = cmd!("sh", "-c", "echo foo >&2").stderr_capture().run().unwrap();
assert_eq!(&b"foo\n"[..], &output_obj.stderr[..]);
source

pub fn stderr_to_stdout(&self) -> Expression

Join the standard error of an expression to its standard output pipe, similar to 2>&1 in the shell.

Example
let error_output = cmd!("sh", "-c", "echo foo >&2").stderr_to_stdout().read().unwrap();
assert_eq!("foo", error_output);
source

pub fn stdout_stderr_swap(&self) -> Expression

Swap the stdout and stderr of an expression.

Example
let output = cmd!("sh", "-c", "echo foo && echo bar >&2")
    .stdout_stderr_swap()
    .stdout_capture()
    .stderr_capture()
    .run()
    .unwrap();
assert_eq!(b"bar\n", &*output.stdout);
assert_eq!(b"foo\n", &*output.stderr);
source

pub fn dir<T: Into<PathBuf>>(&self, path: T) -> Expression

Set the working directory where the expression will execute.

Note that in some languages (Rust and Python at least), there are tricky platform differences in the way relative exe paths interact with child working directories. In particular, the exe path will be interpreted relative to the child dir on Unix, but relative to the parent dir on Windows. Duct prefers the Windows behavior, and in order to get that behavior on all platforms it calls std::fs::canonicalize on relative exe paths when dir is in use. Paths in this sense are any program name containing a path separator, regardless of the type. (Note also that Path and PathBuf program names get a ./ prepended to them automatically by the IntoExecutablePath trait, and so will always contain a separator.)

Errors

Canonicalization can fail on some filesystems, or if the current directory has been removed, and run will return those errors rather than trying any sneaky workarounds.

Example
let output = cmd!("pwd").dir("/").read().unwrap();
assert_eq!("/", output);
source

pub fn env<T, U>(&self, name: T, val: U) -> Expression
where T: Into<OsString>, U: Into<OsString>,

Set a variable in the expression’s environment.

Example
let output = cmd!("sh", "-c", "echo $FOO").env("FOO", "bar").read().unwrap();
assert_eq!("bar", output);
source

pub fn env_remove<T>(&self, name: T) -> Expression
where T: Into<OsString>,

Remove a variable from the expression’s environment.

Note that all the environment functions try to do whatever the platform does with respect to case sensitivity. That means that env_remove("foo") will unset the uppercase variable FOO on Windows, but not on Unix.

Example
std::env::set_var("TESTING", "true");
let output = cmd!("sh", "-c", "echo a${TESTING}b")
    .env_remove("TESTING")
    .read()
    .unwrap();
assert_eq!("ab", output);
source

pub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
where T: IntoIterator<Item = (U, V)>, U: Into<OsString>, V: Into<OsString>,

Set the expression’s entire environment, from a collection of name-value pairs (like a HashMap). Note that some environment variables are required for normal program execution (like SystemRoot on Windows), so copying the parent’s environment is usually preferable to starting with an empty one.

Example
let mut env_map: HashMap<_, _> = std::env::vars().collect();
env_map.insert("FOO".into(), "bar".into());
let output = cmd!("sh", "-c", "echo $FOO").full_env(&env_map).read().unwrap();
assert_eq!("bar", output);
// The IntoIterator/Into<OsString> bounds are pretty flexible. Passing
// by value works here too.
let output = cmd!("sh", "-c", "echo $FOO").full_env(env_map).read().unwrap();
assert_eq!("bar", output);
source

pub fn unchecked(&self) -> Expression

Prevent a non-zero exit status from causing run or read to return an error. The unchecked exit code will still be there on the Output returned by run; its value doesn’t change.

“Uncheckedness” sticks to an exit code as it bubbles up through complicated pipelines, but it doesn’t “infect” other exit codes. So for example, if only one sub-expression in a pipe has unchecked, then errors returned by the other side will still be checked. That said, most commonly you’ll just call unchecked right before run, and it’ll apply to an entire expression.

Example

Note the differences among these three cases:

// Don't check errors on the left side.
cmd!("foo").unchecked().pipe(cmd!("bar")).run()?;

// Don't check errors on the right side.
cmd!("foo").pipe(cmd!("bar").unchecked()).run()?;

// Don't check errors on either side.
cmd!("foo").pipe(cmd!("bar")).unchecked().run()?;
source

pub fn before_spawn<F>(&self, hook: F) -> Expression
where F: Fn(&mut Command) -> Result<()> + Send + Sync + 'static,

Add a hook for modifying std::process::Command objects immediately before they’re executed.

The hook is called for each command in its sub-expression, and each time the expression is executed. The call happens after other features like stdout and env have been applied, so any changes made by the hook take priority. More than one hook can be added, in which case the innermost is executed last. For example, if one call to before_spawn is applied to an entire pipe expression, and another call is applied to just one command within the pipe, the hook for the entire pipeline will be called first over the command where both hooks apply.

This is intended for rare and tricky cases, like callers who want to change the group ID of their child processes, or who want to run code in before_exec. Most callers shouldn’t need to use it.

Example
let output = cmd!("echo", "foo")
    .before_spawn(|cmd| {
        // Sneakily add an extra argument.
        cmd.arg("bar");
        Ok(())
    })
    .read()
    .unwrap();
assert_eq!("foo bar", output);

Trait Implementations§

source§

impl Clone for Expression

source§

fn clone(&self) -> Expression

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Expression

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> From<&'a Expression> for Expression

source§

fn from(expr: &Expression) -> Expression

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.