Piping stderr strips formatting/colors


#1

When I grab stderr using Stdio::piped(), I am able to get the contents of stderr, but any formatting is lost. I’ve attempted this using both a String and using a Vec<u8> and in both occasions, the formatting is stripped and plain text is returned to the terminal.

Rough version of the code. Full code available at command.rs

fn do_something(mut cmd: Command) {
  let mut cmd = cmd.stdin(Stdio::inherit())
                   .stdout(Stdio::inherit())
                   .stderr(Stdio::piped())
                   .spawn()
                   .unwrap();
  let mut buffered_stderr = BufReader::new(cmd.stderr.take().unwrap());
  let status = cmd.wait();

  match status {
    Err(e) => whatever(e),
    Ok(status) => {
      let stderr = io::stderr();
      let mut handle = stderr.lock();
      
      while buffered_stderr.read_line(&mut buffer).unwrap() > 0 {
        let b = buffer.to_owned();
        buffer.clear();
        let _ = handle.write(b.as_bytes());
        // more stuff here
      }
    }
  }
}

Is there a way to maintain coloring in stderr?

Edit including a screenshot so we can stop talking about --color always


#2

That’s a feature of the command you are calling, as far as I know, so you’ll probably have to convince the command that stderr is a terminal, and not actually piped. I don’t know how that would be done, though.


#3

Bah! That was my fear. cmd is calling out to rustc. The goal is to collect the errors thrown during compilation if the user has enabled telemetry.


#4

Many commands allow forcing colors. In particular rustc --color always


#5

While that is true, that doesn’t fix the stderr pipe stripping the colors.


#6

After fixing the broken example I can see colors just fine with --color always. (On Fedora Linux)

use std::process::{Command, Stdio};
use std::io::BufReader;
use std::io::prelude::*;
use std::io;

fn do_something(mut cmd: Command) {
  let mut cmd = cmd.stdin(Stdio::inherit())
                   .stdout(Stdio::inherit())
                   .stderr(Stdio::piped())
                   .spawn()
                   .unwrap();
  let mut buffered_stderr = BufReader::new(cmd.stderr.take().unwrap());
  let status = cmd.wait();

  match status {
    Err(_) => panic!(),
    Ok(_) => {
      let stderr = io::stderr();
      let mut handle = stderr.lock();
      let mut buffer = String::new();
      
      while buffered_stderr.read_line(&mut buffer).unwrap() > 0 {
        let b = buffer.to_owned();
        buffer.clear();
        let _ = handle.write(b.as_bytes());
        // more stuff here
      }
    }
  }
}

fn main() {
    let mut cmd = Command::new("rustc");
    cmd.arg("--color").arg("always").arg("qwe.rs");
    do_something(cmd);
}

#7

I can see colors just fine with --color always. (On Fedora Linux)

Thanks for checking. I’m only running into this on Windows, so it’s looking more like a platform specific issue.


#8

On Windows (at least prior to 10) colors can only be controlled out-of-band so there’s nothing you can do. See also Introducing Colored: the most simple way to add colors in your term!


#9

Oh, very interesting. I’m not super worried about Windows 7 since it went out of mainstream support 3 months ago. Windows 8 is more worrying.

Thanks for that link, I’ll dig into what’s going on and see if we can get things working on Windows 10.


#10

A slight point of clarification: the pipe itself is not stripping the colors; the program you are invoking is detecting the pipe and choosing not to send colors. (On older versions of Windows there was no way to send color information on a pipe; Unix supports it but “programs that want to use color should check for pipes first” has been a cultural norm since forever).