6

I need to redirect output of a spawned child process. This is what I tried but it doesn't work:

Command::new(cmd)
    .args(&["--testnet",
            "--fast",
            format!("&>> {}", log_path).as_str()])
    .stdin(Stdio::piped())
    .stdout(Stdio::inherit())
    .spawn()
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
Constantine
  • 1,802
  • 3
  • 23
  • 37

3 Answers3

9

You can't redirect output with > when starting another program like that. Operators like >, >>, | and similar ones are interpreted by your shell and are not a native functionality of starting programs. Since the Command API doesn't emulate a shell, this won't work. So instead of passing it in args you have to use other methods of the process API to achieve what you want.

Short lived program

If the program to start is usually finished immediately, you might just want to wait until it's done and collect its output then. Then you can simply use the Command::output():

use std::process::Command;
use std::fs::File;
use std::io::Write;

let output = Command::new("rustc")
    .args(&["-V", "--verbose"])
    .output()?;

let mut f = File::create("rustc.log")?;
f.write_all(&output.stdout)?;

(Playground)

Note: the code above has to be in a function that returns a Result in order for the ? operator to work (it just passes errors up).

Long lived program

But maybe your program is not short lived and you don't want to wait until it's finished before doing anything with the output. In that case you should capture stdout and call Command::spawn(). Then you can access the ChildStdout which implements Read:

use std::process::{Command, Stdio};
use std::fs::File;
use std::io;

let child = Command::new("ping")
    .args(&["-c", "10", "google.com"])
    .stdout(Stdio::piped())
    .spawn()?;

let mut f = File::create("ping.log")?;
io::copy(&mut child.stdout.unwrap(), &mut f)?;

(Playground)

That way, ping.log is written on the fly every time the command outputs new data.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Thanks, I already tried `io::copy` but it is not working for me, looks like I am missing somethings. I am trying to watch log with `tail` but stays empty – Constantine May 13 '17 at 07:37
  • 2
    @KostyaKrivomaz Have you tried `tail -f`? Otherwise the view won't update. And I just tried, and it does indeed work for me... – Lukas Kalbertodt May 13 '17 at 07:45
  • Yeap, tried with `-f`. Just executed your example, and still nothing. There is some problem on my side, down further, will continue searching. Any way, thanks. – Constantine May 13 '17 at 09:39
  • I think the right argument for `ping` is `-c` instead of `-n` – Thomas Gotwig Sep 16 '20 at 12:25
6

To directly use a file as output without intermediate copying from a pipe, you have to pass the file descriptor. The code is platform-specific, but with conditional compilation you can make it work on Windows too.

let f = File::create("foo.log").unwrap();
let fd = f.as_raw_fd();
// from_raw_fd is only considered unsafe if the file is used for mmap
let out = unsafe {Stdio::from_raw_fd(fd)};
let child = Command::new("foo")
    .stdout(out)
    .spawn().unwrap();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
the8472
  • 40,999
  • 5
  • 70
  • 122
6

They have added a new (in 7/6/2017 https://github.com/rust-lang/rust/pull/42133) std::process::Stdio::from function that also accepts files and works on both windows and unix systems. example:

use std::fs::File;
use std::process::Command;

fn main() {
    let f = File::create("path/to/some/log.log").unwrap();
    let mut cmd = Command::new("some command")
        .stdout(std::process::Stdio::from(f))
        .spawn()
        .unwrap();
    cmd.wait().unwrap();
}
Flair
  • 2,609
  • 1
  • 29
  • 41