Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getting the command's exit status after using stderr_to_stdout #114

Open
ezntek opened this issue Sep 15, 2023 · 4 comments
Open

getting the command's exit status after using stderr_to_stdout #114

ezntek opened this issue Sep 15, 2023 · 4 comments

Comments

@ezntek
Copy link

ezntek commented Sep 15, 2023

Is it possible for me to call .stderr_to_stdout() on an expression and also get an exit status?

@oconnor663
Copy link
Owner

How about this?

fn main() -> anyhow::Result<()> {
    let bash_script = "echo hi && echo lo 1>&2 && exit 42";
    let output = duct::cmd!("bash", "-c", bash_script)
        .stderr_to_stdout()
        .stdout_capture()
        .unchecked()
        .run()?;
    assert_eq!(output.status.code(), Some(42));
    assert_eq!(&output.stdout, b"hi\nlo\n");
    Ok(())
}

@ezntek
Copy link
Author

ezntek commented Jan 20, 2024

I'm using the lines method to get the stdout and stderr with a BufReader, but trying to access the exit code through the ReaderHandle would be illegal. because the ReaderHandle would have been moved. How do I mitigate this? (get a stream and get the exit code later with try_wait)

@oconnor663
Copy link
Owner

oconnor663 commented Jan 21, 2024

Ah this is a bit tricky. The .lines() method on BufReader normally consumes the whole reader, but it's also possible to call .lines() on a &mut BufReader. (There's a blanket impl on BufRead that makes this work.) If you do that, then you can get the original duct::ReaderHandle by calling BufReader::into_inner. The whole operation looks something like this:

use duct::cmd;
use std::io::prelude::*;
use std::io::BufReader;

const PYTHON: &str = r#"
import sys
for i in range(1_000_000):
    sys.stdout.write(f"stdout {i}\n")
    sys.stderr.write(f"stderr {i}\n")
"#;

fn main() -> anyhow::Result<()> {
    let reader = cmd!("python3", "-c", PYTHON).stderr_to_stdout().reader()?;
    let mut buf_reader = BufReader::new(reader);
    let num_lines = (&mut buf_reader).lines().count();
    println!("counted {num_lines} lines");
    let reader = buf_reader.into_inner();
    println!("exit status: {:?}", reader.try_wait()?.unwrap());
    Ok(())
}

@oconnor663
Copy link
Owner

That said, ReaderHandle is designed so that this usually isn't necessary. Take a look at the docs:

When this reader reaches EOF, it automatically calls wait on the inner handle. If the child returns a non-zero exit status, the read at EOF will return an error, unless you use unchecked.

So if what you want is to read the handle all the way to the end and then return an error if the child's status was non-zero, that's what happens automatically! Take a look at this example:

use duct::cmd;
use std::io::prelude::*;
use std::io::BufReader;

const PYTHON: &str = r#"
print("one")
print("two")
print("three")
assert 1 == 2  # raise an exception and ultimately exit with a non-zero status
"#;

fn main() -> anyhow::Result<()> {
    let reader = cmd!("python3", "-c", PYTHON)
        .env("PYTHONUNBUFFERED", "1")
        .stderr_to_stdout()
        .reader()?;
    for line in BufReader::new(reader).lines() {
        println!("read a line: {:?}", line?);
    }
    println!("We never get here!");
    Ok(())
}
$ cargo run
cargo run         
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/scratch`
read a line: "one"
read a line: "two"
read a line: "three"
read a line: "Traceback (most recent call last):"
read a line: "  File \"<string>\", line 5, in <module>"
read a line: "AssertionError"
Error: command ["python3", "-c", "\nprint(\"one\")\nprint(\"two\")\nprint(\"three\")\nassert 1 == 2  # raise an exception and ultimately exit with a non-zero status\n"] exited with code 1
$ echo $?
1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants