How to redirect child process output to stderr?
Asked Answered
D

3

11

I am trying to start a process with the Command API and redirect its standard output to standard error. The following fails:

Command::new("tput")
    .arg("rc")
    .stdout(io::stderr())
    .status()
    .expect("failed to run tput");

because Command::new("tput").arg("rc").stdout(<XXX>) expects a std::process::Stdio:

expected struct `std::process::Stdio`, found struct `std::io::Stderr`

The equivalent in Bash would probably be tput rc > /dev/stderr.

I would like to know how to do this properly.

Deduct answered 7/2, 2017 at 23:27 Comment(1)
Briefly looking at the API I'd say this is not possible. Perhaps an issue should be raised for this where the stdout method could accept a Writer instance instead?Smut
U
5

As of Rust 1.74 which contains rust-lang/rust#114590, the code works exactly as you originally tried it. This didn't used to work in older versions of Rust.

Command::new("tput")
    .arg("rc")
    .stdout(io::stderr())
    .status()
    .expect("failed to run tput");
Ulcer answered 17/8 at 5:50 Comment(0)
C
8

As of Rust 1.15.0, Stdio doesn't expose this functionality in a portable API, but there are platform-specific extension traits that you can use for this purpose.

On Unix-like platforms, the std::os::unix::io::FromRawFd trait is implemented on Stdio. This trait provides a single method, from_raw_fd, that can turn a file descriptor into the type that implements the trait. Since standard error is defined as file descriptor 2, you could simply use .stdout(Stdio::from_raw_fd(2)).

On Windows, there's a similar trait called FromRawHandle implemented on Stdio. Unfortunately, it's not listed in the online documentation; it only contains the Unix-specific variants. You would call GetStdHandle(STD_ERROR_HANDLE) to obtain a handle to the standard error.

Clinical answered 8/2, 2017 at 1:37 Comment(0)
U
5

As of Rust 1.74 which contains rust-lang/rust#114590, the code works exactly as you originally tried it. This didn't used to work in older versions of Rust.

Command::new("tput")
    .arg("rc")
    .stdout(io::stderr())
    .status()
    .expect("failed to run tput");
Ulcer answered 17/8 at 5:50 Comment(0)
M
1

This worked on linux. Pulling in the AsFd trait allows us to get a BorrowedFd from stderr. The try_clone_to_owned method was hard to find, but it gives a OwnedFd which can automatically convert into a process::Stdio, the type required to configure stderr of child processes.

use std::os::fd::AsFd;

let stderr = std::io::stderr().as_fd().try_clone_to_owned()?;
std::process::Command::new("terraform")
    .arg("fmt")
    .arg(&outpath)
    .stdout(stderr)
    .status()
    .unwrap_or_else(|_| panic!("terraform fmt failed: {:?}", outpath));

Compacted into a function:

fn stderr() -> Result<std::process::Stdio, Error> {
    use std::os::fd::AsFd;
    let stderr = std::io::stderr().as_fd().try_clone_to_owned()?;
    Ok(stderr.into())
}
Melcher answered 7/5, 2023 at 3:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.