Reading stdout does not capture output without newline

I'm trying to capture stdout from accoreconsole, a cli version of AutoCAD. I don't understand why but for some reason the last output from accoreconsole (which doesn't seem to have any newline character) does not get captured until there are a newline. Even when the application exits with what seems to be without a newline it does not get captured by my BufReader. But when I run it in a regular terminal it works just fine and I see all output (the last Command: line). Same thing when accoreconsole prompts the user for input.

Here's what I've tried:

use std::io::stdout;
use std::io::Write;
use std::str;
use tokio::io::{AsyncReadExt, BufReader};
use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut child = Command::new("c:\\Program Files\\Autodesk\\Autocad 2024\\accoreconsole.exe")
        .args(&["/i", r"c:\dev\1.dwg", "/s", r"c:\dev\test.scr"])
        .stdout(std::process::Stdio::piped())
        .spawn()
        .unwrap();

    let proc_stdout = child.stdout.take().expect("Failed to open stdout");
    let mut reader = BufReader::new(proc_stdout);
    let mut buffer = [0; 1];

    loop {
        let bytes_read = reader.read(&mut buffer).await.unwrap();

        if bytes_read > 0 {
            let string_buf = str::from_utf8(&buffer[..bytes_read]).unwrap();
            print!("{}", string_buf);
            let _ = stdout().flush();
        } else {
            // there should be logic to handle EOF somewhere here...
        }
    }

    Ok(())
}

I'm not even able to recreate the issue using unflushed stdout:

use std::io::stdout;
use std::io::Write;

fn main() {
    let mut counter = 0;
    let output = "Hello world!".to_string();

    loop {
        let mut char_iter = output.chars();

        while let Some(c) = char_iter.next() {
            print!("{}", c);
            let _ = stdout().flush();
            std::thread::sleep(std::time::Duration::from_millis(50));
        }

        println!("_");

        if counter == 3 {
            print!("#### gets flushed on application exit");
            std::thread::sleep(std::time::Duration::from_millis(1000));
            break;
        }

        counter += 1;
    }
}

The "#### gets flushed on application exit"-part is flushed and captured when the application exits.

Are there different ways to output to stdout? Or why doesn't my Rust application capture the prompts or last output of the accoreconsole process?

Thanks!

Is there a possibility the missing pieces are on stderr?

No, I don't think so. The output will show up in stdout if/when a newline is added.

Does it matter that you never wait for child to terminate? Can you just await wait_with_output?

You could try process monitor to see how the child process chunks its writes at the syscall level.

You were right!! Even though it's totally unintuitive to be able to get ALL of the output through stderr instead of stdout. There are no errors, just regular stdout but it's being sent to both stdout and stderr for some reason.

Thanks for suggesting to check stderr. I was 100% sure I didn't need to check there so it's likely I'd never try that otherwise.

Also remember shells are complicated. For instance, accoreconsole may change it's behavior depending on if it on a tty or being piped.

Sorry all. I'm being an idiot here... I thought I switched stdout to stderr when what I did was I basically removed the
.stdout(std::process::Stdio::piped())-line and added one for stderr causing the stdout to NOT be piped to my application and instead being handled by the terminal I was running the application in, which does handle stdout correctly... fooling me to think that was the solution.

As I first thought nothing is being piped to stderr by accoreconsole. And I'm back at square one where the stdout is not getting the output that doesn't end with newline.

I have done the same thing using Nodejs. It doesn't flush unless there's a newline but at least I get all the output when the application exits...

    // _process is a ChildProcess
    _process.stdout.on("data", (data: Buffer) => {
      const message = data.toString();
      // ...
    });

So that makes me curious why Rust isn't able to output it the same way.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.