我希望我的应用程序能够通过
source > my_app
从重定向的文件流读取输入,但我不希望它在没有重定向的情况下要求用户输入。
C 提供了这样的方法(检查标准输入缓冲区是否为空),但我找不到 Rust 的任何解决方案。
与其他用户建议一样,
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.*"
这里有两个单独的问题。
这个答案是针对第二个问题的。我还将其编写为带有测试用例的独立小提琴: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
}
}
我今天正在寻找这个问题,这个答案已经有 9 个月了,所以我添加了我使用的本机解决方案:
Stdin::is_terminal()
let input = std::io::stdin();
if input.is_terminal() {
// no input available
} else {
// input available
}
未在不同平台上进行测试,仅在 Mac 上进行测试。
它让我能够:
cargo run
cargo run < example.txt