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
impl Expression
Sourcepub fn run(&self) -> Result<Output>
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);
Sourcepub fn read(&self) -> Result<String>
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);
Sourcepub fn start(&self) -> Result<Handle>
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);
Sourcepub fn reader(&self) -> Result<ReaderHandle>
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);
Sourcepub fn pipe<T: Into<Expression>>(&self, right: T) -> Expression
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());
Sourcepub fn stdin_bytes<T: Into<Vec<u8>>>(&self, bytes: T) -> Expression
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);
Sourcepub fn stdin_path<T: Into<PathBuf>>(&self, path: T) -> Expression
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);
Sourcepub fn stdin_file<T: IntoRawFd>(&self, file: T) -> Expression
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);
Sourcepub fn stdin_null(&self) -> Expression
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);
Sourcepub fn stdout_path<T: Into<PathBuf>>(&self, path: T) -> Expression
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);
Sourcepub fn stdout_file<T: IntoRawFd>(&self, file: T) -> Expression
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);
Sourcepub fn stdout_null(&self) -> Expression
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);
Sourcepub fn stdout_capture(&self) -> Expression
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)
Sourcepub fn stdout_to_stderr(&self) -> Expression
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[..]);
Sourcepub fn stderr_path<T: Into<PathBuf>>(&self, path: T) -> Expression
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);
Sourcepub fn stderr_file<T: IntoRawFd>(&self, file: T) -> Expression
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);
Sourcepub fn stderr_null(&self) -> Expression
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();
Sourcepub fn stderr_capture(&self) -> Expression
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[..]);
Sourcepub fn stderr_to_stdout(&self) -> Expression
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);
Sourcepub fn stdout_stderr_swap(&self) -> Expression
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);
Sourcepub fn dir<T: Into<PathBuf>>(&self, path: T) -> Expression
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);
Sourcepub fn env<T, U>(&self, name: T, val: U) -> Expression
pub fn env<T, U>(&self, name: T, val: U) -> Expression
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);
Sourcepub fn env_remove<T>(&self, name: T) -> Expression
pub fn env_remove<T>(&self, name: T) -> Expression
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);
Sourcepub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
pub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
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);
Sourcepub fn unchecked(&self) -> Expression
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()?;
Sourcepub fn before_spawn<F>(&self, hook: F) -> Expression
pub fn before_spawn<F>(&self, hook: F) -> Expression
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
impl Clone for Expression
Source§fn clone(&self) -> Expression
fn clone(&self) -> Expression
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read more