13

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
AbhiNickz
  • 1,035
  • 2
  • 14
  • 32

4 Answers4

16

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();
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • If I understand correctly `lock()`, this will break the purpose of a pipe, `find` will write its output in a stream, but you are currently lock and read only what find has already read. A `while` loop with `read_line()` will not be more idiomatic and functional ? – Stargateur Apr 09 '18 at 14:36
  • @Stargateur that is incorrect. `lock` means that nothing else in the process may read from STDIN, not that it will stop reading data from outside the process. This has more performance than the `loop` suggested elsewhere (but is not the fastest possibility) and iterators are very idiomatic. – Shepmaster Apr 09 '18 at 16:20
  • tools like `grep` doesn't lock when its invoked alone. How does that work? – Hanif Bin Ariffin Feb 15 '21 at 11:37
6

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.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks Peter, I did found this after posting this question, but this fails to read the output greater than 1 line. – AbhiNickz Apr 09 '18 at 13:32
  • 1
    You can just do this in a loop, and bail out when the line has zero length. I'll update my answer with that. – Peter Hall Apr 09 '18 at 15:09
2
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
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
AbhiNickz
  • 1,035
  • 2
  • 14
  • 32
1

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);

}

Fraser
  • 290
  • 3
  • 11