oconnor663 / duct.rs

a Rust library for running child processes
MIT License
795 stars 34 forks source link

Is there a way to view `stderr` output as if it were attached to a TTY? #98

Closed bradleyharden closed 2 years ago

bradleyharden commented 2 years ago

I've been using duct in a small script of mine. For the most part, it's been great. But I really struggled to figure this one out.

I'm trying to do the bash equivalent of

$ wget -q --show-progress -O - $link | gzip -dc | tar -x -C $dst

Unfortunately, I couldn't figure out how to express this with duct. I think the main problem is that wget sees that it is not attached to a TTY, so it changes its progress bar output. I can capture stderr, but it's very different than what I get when I run the command manually. On top of that, it seems like the progress bar updates come at an enormous rate. But if I don't capture stderr, I don't see any output at all.

I eventually dropped back to using std::process, and I got everything to work the first time. Here's what I came up with

use std::process::{Command, Stdio};
let mut wget = Command::new("wget")
    .args(["-q", "--show-progress", "-O", "-", &link])
    .stdout(Stdio::piped())
    .spawn()?;
let wget_stdout = wget.stdout.take().unwrap();
let mut gzip = Command::new("gzip")
    .arg("-dc")
    .stdin(wget_stdout)
    .stdout(Stdio::piped())
    .spawn()?;
let gzip_stdout = gzip.stdout.take().unwrap();
let mut tar = Command::new("tar")
    .args(["-x", "-C"])
    .arg(dst)
    .stdin(gzip_stdout)
    .spawn()?;
wget.wait()?;
gzip.wait()?;
tar.wait()?;

I don't have an exact record of everything I tried with duct, but they were all close to this

let wget = cmd!("wget", "-q", "--show-progress", "-O", "-", &link);
let gzip = cmd!("gzip", "-dc");
let tar = cmd!("tar", "-x", "-C", dst);
let _ = wget.pipe(gzip.pipe(tar)).run()?;

What's the difference here? Does duct not have stderr inherit by default, like spawn? I can't seem to figure out why the behavior is different.

oconnor663 commented 2 years ago

Hmm. Duct does let stderr inherit by default, so I would've expected this to work. And it might be that it's working for me. Here's what I'm running in Bash:

$ wget -q --show-progress -O - "https://mirror.math.princeton.edu/pub/ubuntu-iso/trusty/ubuntu-14.04.6-desktop-amd64.iso" | wc -c
SSL_INIT
-                    0%[              ]   6.21M  1.56MB/s    eta 14m 20s

(Not sure why it prints SSL_INIT but whatever.) And here's what I'm running in Rust:

use duct::cmd;

fn main() -> anyhow::Result<()> {
    let link =
        "https://mirror.math.princeton.edu/pub/ubuntu-iso/trusty/ubuntu-14.04.6-desktop-amd64.iso";
    let wget = cmd!("wget", "-q", "--show-progress", "-O", "-", &link);
    let wc = cmd!("wc", "-c");
    let _ = wget.pipe(wc).run()?;
    Ok(())
}
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/scratch`
SSL_INIT
-                    0%[              ]   2.85M   921KB/s    eta 20m 25s

Sounds like that's different from what you're seeing in the second case?

bradleyharden commented 2 years ago

Wow, quick reply. Yes, I believe that's not what I observed. Let me make sure everything I said is correct though. I'll double check.

bradleyharden commented 2 years ago

Well, never mind I guess. It looks like it works for me too. I must have misunderstood something at first, confused myself, and ended up going down a long, pointless road. Sorry for the false alarm.

oconnor663 commented 2 years ago

Interesting. No worries. And if you ever figure out what it was, feel free to reopen this ticket.