有没有一种 Rust 方法可以在尝试读取 STDIN 缓冲区之前检查它是否为空?

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

我希望我的应用程序能够通过 source > my_app 从重定向的文件流中读取输入,但我不希望它在没有重定向的情况下要求用户输入。

C 提供了这样的方法 (Checking the stdin buffer if it's empty) 但我找不到任何 Rust 的解决方案。

作为 trentcl suggested, the way to do it is atty 板条箱。

这里有两个不同的问题。

  1. “我不希望它在没有重定向的情况下要求用户输入。” - 正如@trentcl 所述,最佳答案是 atty
  2. “有没有办法检查标准输入缓冲区是否为空?”

这个答案是针对第二个问题的。我还把它写成一个自包含的 fiddle 和测试用例:https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a265b9c4e76d6a9e53d8cc3e5334e42c.

Rust 标准库没有提供方法。我已经转到底层 OS 文件描述符,因此我可以在阻塞和非阻塞模式之间切换它。所以:从一个 FD 开始,我们将围绕它构建一个普通的 BufReader:

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),
    }
  }
}

但是因为我们知道我们的 BufReader 包装了一个 FD(而不是一些任意的 Reader)我们可以到达那个 FD 将它切换到非阻塞:(这个方法进入内部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
  }
}

为了完整性,这里是正常的 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
  }
}

喜欢其他用户, atty is a good way to check for this but it seems that there are some cases you should know as mentioned here

我会留下这个示例代码以备不时之需and/or供我自己将来参考:

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.*"