Hijacking stderr in Rust

[caption id=“attachment_1034” align=“alignright” width=“300”]Liberate your output pipes from the tyranny of programmatic control! Liberate your output pipes from the tyranny of programmatic control![/caption] Let’s say you have to work with OS processes - you’re building tools for automation and you need to capture the output of a command with your Rust program. You might need to process the command’s output and do something with it, but you also need to send that output back to the usual location (usually stderr or stdout). We can do this using the stdin(), stdout(), and stderr() functions of our Command. In all of these examples, I’m going to assume that you have a working Command already set up.

Piping Our Command’s Error Stream

In this case, we have a command assigned to a variable named cmd. In order to hijack one of the command’s IO streams we can do something like this:``` let mut cmd = cmd.stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::piped()) .spawn() .unwrap();

```This sets up our command’s IO streams - both stdin and stdout will be inherited by the program. In other words, our program won’t touch the stdin and stdout of cmd; stdin and stdout are just passed through back to the user. stderr, on the other hand, will be connect to our process. Calling spawn() executes the command as a child process and returns a Result<Child> that points to the child process. At this point, we have created the child process, but we haven’t done anything with it and we haven’t set anything up to handle the stderrstream.

Consuming the Piped Output

Now that we’ve set up the command to pipe stderr into our program, we need to be able to consume it. There are a number of ways to do this, but in our case, we’re going to use a buffered reader. We want to avoid low level Read calls (this is for reading bytes from a source stream) and we also want to avoid waiting until the child process is done before consuming stderr.let mut buffered\_stderr = BufReader::new(cmd.stderr.take().unwrap()); let status = cmd.wait();In this chunk of code we create a BufReader (our buffered reader) over the command’s stderr stream. We then call cmd.wait() and get the exit status of our process. We now have a buffered reader that we can use to consume the child command’s stderr stream. The command should have produced a status and we can now do something with it.

Actually Processing the Piped Output

Now we’re ready to do something with our command’s output. I’ll leave out the match around dealing with the command’s success or failure, and get right into both processing stderr and sending it back to the end user.``` // allocate a new string to be used as a buffer for stderr let mut buffer = String::new();

// lock stderr and give us a handle we can write to let stderr = io::stderr(); let mut handle = stderr.lock();

// read stderr line by line while buffered_stderr.read_line(&mut buffer).unwrap() > 0 { // grab our copy of the output, clear the original buffer, // and then write the bytes to stderr let b = buffer.to_owned(); buffer.clear(); let _ = handle.write(b.as_bytes());

// do something with each line of captured stderr

} ```In this little example, we set up string to use as a buffer for storing pieces of the stderr stream. Then we grab a handle to stderr. This handle prevents someone else from writing to our process’s stderr. Then, as the buffered reader (buffered_stderr) reads each line, we write it into our buffer, make a copy, clear the buffer, and send the buffer to stderr. We have call buffer.clear() because, otherwise, the next time the while loop’s contents execute, the next line from stderr will be appended to our buffer. Using clear() empties out the buffer so it’s ready for the next round.

Wasn’t that easy?

It sure was! It’s easy enough to grab the output of a child process and perform some kind of work on it while still streaming the child’s output through your own process. Users don’t have to know what’s going on, they just get to know that their process is running and prodducing the output they’ve always expected.


P1230766” by régine debatty licensed with CC BY-SA 2.0