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

问题描述 投票:0回答:4

我希望我的应用程序能够通过

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

C 提供了这样的方法(检查标准输入缓冲区是否为空),但我找不到 Rust 的任何解决方案。

rust stdin
4个回答
2
投票

与其他用户建议一样,

atty
是检查此问题的好方法,但似乎有些情况您应该了解,如此处所述。

我将留下此示例代码,以防有人需要它和/或供我自己将来参考:

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

0
投票

正如 trentcl 所建议的,这样做的方法是 atty crate。


0
投票

这里有两个单独的问题。

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

这个答案是针对第二个问题的。我还将其编写为带有测试用例的独立小提琴:https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a265b9c4e76d6a9e53d8cc3e5334e42c.

Rust 标准库没有提供方法。我已经转到底层操作系统文件描述符,因此我可以在阻塞和非阻塞模式之间切换它。所以:从 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
  }
}

0
投票

我今天正在寻找这个问题,这个答案已经有 9 个月了,所以我添加了我使用的本机解决方案:

Stdin::is_terminal()

  let input = std::io::stdin();
  if input.is_terminal() {
    // no input available
  } else {
    // input available
  }

未在不同平台上进行测试,仅在 Mac 上进行测试。

它让我能够:

  1. cargo run
  2. 出现错误
  3. 成功
    cargo run < example.txt
© www.soinside.com 2019 - 2024. All rights reserved.