Double redirection stdout/stderr


#1

Hi !
I’m trying to port a bash script to rust, as an exercise.
In this script, I’m doing ./foo bar > /tmp/log 2>&1 to redirect both stdout and stderr to /tmp/log.

In rust, so far, I’m doing

let stdout_file = File::create(&logfile).expect("Can't create log (out) file");
let stderr_file = File::create(&logfile).expect("Can't create log (err) file");
Command::new(&prog)
        .args(&args)
        .stdout(Stdio::from(stdout_file))
        .stderr(Stdio::from(stderr_file))
        .output()
        .expect("failed to execute command");

But I’m not sure I am doing things ok… How would you do the equivalent of 2>&1 in rust ?


#2

Hmm, I don’t think that is actually possible using the Command provided by the standard library. In C you’d probably just copy the file handle (it’s just an integer after all), however I don’t think that’ll work because the FromRawFd trait (and its windows equivalent) consume the file handle when you construct a Stdio out of them.

Something else you could try is to spawn the process then continually try to read the ChildStdout and ChildStdin into your file. That feels really hacky though.

Do keep in mind that redirecting stdout/stderr like this would effectively be allowing unsynchronised mutation of the same resource (your log file) from two separate things. I imagine bash would probably buffer things by line to prevent corrupting output too much.


#3

With both RawFd = c_int and RawHandle = HANDLE = *mut c_void, they shouldn’t really be consumed, just copied.


#4

My understanding is that, conceptually, they are consumed because the handle gets closed on drop. I’m not sure if the OS will complain if you try to drop the same file handle twice though.

This function consumes ownership of the specified file descriptor. The returned object will take responsibility for closing it when the object goes out of scope.

This function is also unsafe as the primitives currently returned have the contract that they are the sole owner of the file descriptor they are wrapping. Usage of this function could accidentally allow violating this contract which can cause memory unsafety in code that relies on it being true.


#5

Ah, yes, of course. That is a problem.

The next approach I’d suggest is to dup the fd, but I’m not sure if that functionality is exposed in the standard library.


#6

That’s a good idea. Is there any reason why std::process::Stdio couldn’t implement Clone, using dup() under the hood to clone the file descriptor? Other than there probably never really being a need for it in the past.


#7

Actually, there is File::try_clone(&self), which maps to fcntl(fd, FD_DUPFD_CLOEXEC) on unix and DuplicateHandle on windows. It’s not a plain Clone implementation because it can fail, as with most IO functions. I think this should suffice!


#8

Thanks for all the feedback ! I’ll have a look at the File::try_clone method :slight_smile:


#9

Cool! This looks like a prime idea for cookbook example.


#10

@vsiles a cookbook recipe was added here


#11

Nice ! Thank you very much for that.