Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture the output of a process piped into a Rust program?

I know how to read the command line arguments, but I am having difficulties reading the command output from a pipe.

  1. Connect a program (A) that outputs data to my Rust program using a pipe:

    A | R
    
  2. The program should consume the data line by line as they come.

    $ pwd | cargo run should print the pwd output.

    OR

    $ find . | cargo run should output the find command output which is more than 1 line.

like image 940
AbhiNickz Avatar asked Apr 09 '18 12:04

AbhiNickz


4 Answers

Use BufRead::lines on a locked handle to standard input:

use std::io::{self, BufRead};

fn main() {
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let line = line.expect("Could not read line from standard in");
        println!("{}", line);
    }
}

If you wanted to reuse the allocation of the String, you could use the loop form:

use std::io::{self, Read};

fn main() {
    let stdin = io::stdin();
    let mut stdin = stdin.lock(); // locking is optional

    let mut line = String::new();

    // Could also `match` on the `Result` if you wanted to handle `Err` 
    while let Ok(n_bytes) = stdin.read_to_string(&mut line) {
        if n_bytes == 0 { break }
        println!("{}", line);
        line.clear();
    }
}
like image 60
Shepmaster Avatar answered Oct 18 '22 01:10

Shepmaster


use std::io;

fn main() {
    loop {
        let mut input = String::new();
        io::stdin()
            .read_line(&mut input)
            .expect("failed to read from pipe");
        input = input.trim().to_string();
        if input == "" {
            break;
        }
        println!("Pipe output: {}", input);
    }
}

OUTPUT:

[18:50:29 Abhinickz@wsl -> pipe$ pwd
/mnt/d/Abhinickz/dev_work/learn_rust/pipe
[18:50:46 Abhinickz@wsl -> pipe$ pwd | cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
    Running `target/debug/pipe`
Pipe output: /mnt/d/Abhinickz/dev_work/learn_rust/pipe
like image 20
AbhiNickz Avatar answered Oct 18 '22 01:10

AbhiNickz


You just need to read from Stdin.

This is based on an example taken from the documentation:

use std::io;

fn main() {
    loop {
        let mut input = String::new();
        match io::stdin().read_line(&mut input) {
            Ok(len) => if len == 0 {
                return;
            } else {
                println!("{}", input);
            } 
            Err(error) => {
                eprintln!("error: {}", error);
                return;
            }
        }
    }
}

It's mostly the docs example wrapped in a loop, breaking out of the loop when there is no more input, or if there is an error.

The other changes is that it's better in your context to write errors to stderr, which is why the error branch uses eprintln!, instead of println!. This macro probably wasn't available when that documentation was written.

like image 44
Peter Hall Avatar answered Oct 18 '22 02:10

Peter Hall


You can do it in a pretty snazzy and concise way with rust's iterator methods

use std::io::{self, BufRead};

fn main() {

    // get piped input
    //   eg `cat file | ./program`
    //    ( `cat file | cargo run` also works )

    let input = io::stdin().lock().lines().fold("".to_string(), |acc, line| {
        acc + &line.unwrap() + "\n"
    });

    dbg!(input);

}

like image 1
Fraser Avatar answered Oct 18 '22 01:10

Fraser