Is there a Rust way to check if the STDIN buffer is empty before attempting to read it?
Asked Answered
H

4

8

I want my application to be able to read the input from a redirected file stream through source > my_app, but I do not want it to ask for user input in case there is no redirection.

C provides such methods (Checking the stdin buffer if it's empty) but I could not find any solution for Rust.

Hypogenous answered 31/3, 2020 at 14:26 Comment(3)
C doesn't, you confuse C standard library with OS feature access. C is just capable to call them. (specially the acceptance answer doesn't answer the question cause it's not portable)Rodin
Checking whether data is immediately available isn't a good way to distinguish between interactive and non-interactive use -- you probably want isatty instead (which is also an OS specific feature).Geosyncline
Question text has nothing to do with the title. You ask about the contents of the buffer in title, but the text asks about whether your program is piped or not. Completely different things.Lula
H
0

As trentcl suggested, the way to do it is atty crate.

Hypogenous answered 1/4, 2020 at 5:55 Comment(1)
If you came here and saw the negative votes on this answer, please ignore them. This actually worksAitchbone
B
7

Rust >= 1.70 includes the native solution Stdin::is_terminal():

use std::io::IsTerminal;

let input = std::io::stdin();

if input.is_terminal() {
    // No input available
} else {
    // Input available
}

It allows you to:

  1. Error on cargo run
  2. Succeed on cargo run < example.txt
Bayadere answered 20/1, 2024 at 16:16 Comment(0)
R
2

Like other users suggested, atty is a good way to check for this but it seems that there are some cases you should know as mentioned here.

I will leave this sample code in case anybody needs it and/or for future references for myself:

extern crate atty;

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

fn main() {
    if atty::is(atty::Stream::Stdin) {
        println!("nothing to do here!");
        return;
    }

    io::stdin()
        .lock()
        .lines()
        .for_each(|x| println!("{}", x.expect("error reading line")));
}
[dependencies]
atty="0.2.*"
Rhoea answered 20/12, 2021 at 1:0 Comment(0)
H
0

As trentcl suggested, the way to do it is atty crate.

Hypogenous answered 1/4, 2020 at 5:55 Comment(1)
If you came here and saw the negative votes on this answer, please ignore them. This actually worksAitchbone
D
0

There are two separate questions here.

  1. "I do not want it to ask for user input in case there is no redirection." - as stated by @trentcl, the best answer is atty
  2. "Is there a way to check if the stdin buffer is empty?"

This answer is for the second question. I also wrote it as a self-contained fiddle with test cases: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a265b9c4e76d6a9e53d8cc3e5334e42c.

The Rust standard library doesn't provide a way. I've gone to the underlying OS file descriptor so I can switch it between blocking and non-blocking mode. So: start with an FD, and we'll construct a normal BufReader around it:

struct NonblockingBufReader {
  buffered: BufReader<RawFd2>,
}

struct RawFd2 {
  fd: RawFd,
}

impl NonblockingBufReader {
  /// Takes ownership of the underlying FD
  fn new<R: IntoRawFd>(underlying: R) -> NonblockingBufReader {
    let buffered = BufReader::new(RawFd2 {
      fd: underlying.into_raw_fd(),
    });
    return NonblockingBufReader { buffered };
  }
}

// Here's a private implementation of 'Read' for raw file descriptors,
// for use in BufReader...
impl std::io::Read for RawFd2 {
  fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
    assert!(buf.len() <= isize::max_value() as usize);
    match unsafe { libc::read(self.fd, buf.as_mut_ptr() as _, buf.len()) } {
      x if x < 0 => Err(std::io::Error::last_os_error()),
      x => Ok(x as usize),
    }
  }
}

But because we know our BufReader wraps an FD (rather than just some arbitrary Reader) we can get to that FD to switch it to non-blocking: (this method goes inside impl NonblockingBufReader)

/// Does BufReader::read_line but only if there's already at
/// least one byte available on the FD. In case of EOF, returns
/// an empty string.
/// Possible outcomes: (0) no-data-yet, (1) data, (2) EOF, (3) Error
fn read_line_only_if_data(&mut self) -> std::io::Result<Option<String>> 
{
  let r = unsafe {
    // The reason this is safe is we know 'inner' wraps a valid FD,
    // and we're not doing any reads on it such as would disturb BufReader.
    let fd = self.buffered.get_ref().fd;
    let flags = libc::fcntl(fd, libc::F_GETFL);
    libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
    let r = self.buffered.fill_buf();
    libc::fcntl(fd, libc::F_SETFL, flags);
    r
  };
  // Behavior of fill_buf is "Returns the contents of the internal buffer,
  // filling it with more data from the inner reader if it is empty."
  // If there were no bytes available, then (1) the internal buffer is
  // empty, (2) it'll call inner.read(), (3) that call will error WouldBlock.
  match r {
    Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None), // (0) no-data-yet
    Ok(buf) if !buf.is_empty() => {
      let mut line = String::new();
      self.buffered.read_line(&mut line)?;
      Ok(Some(line)) // (1) data, or further error
    },
    Ok(_) => Ok(Some(String::new())), // (2) EOF
    Err(e) => Err(e), // (3) Error
  }
}

For completeness, here's the normal read_line:

/// Wraps BufReader::read_line.
/// Possible outcomes: (1) data, (2) EOF, (3) Error
fn read_line(&mut self) -> std::io::Result<String> {
  let mut line = String::new();
  match self.buffered.read_line(&mut line) {
    Ok(_) => Ok(line), // EOF/data
    Err(e) => Err(e), // Error
  }
}
Demurrer answered 8/11, 2021 at 13:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.