有没有一种 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 所述,最佳答案是 atty
- “有没有办法检查标准输入缓冲区是否为空?”
这个答案是针对第二个问题的。我还把它写成一个自包含的 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.*"
我希望我的应用程序能够通过 source > my_app
从重定向的文件流中读取输入,但我不希望它在没有重定向的情况下要求用户输入。
C 提供了这样的方法 (Checking the stdin buffer if it's empty) 但我找不到任何 Rust 的解决方案。
这里有两个不同的问题。
- “我不希望它在没有重定向的情况下要求用户输入。” - 正如@trentcl 所述,最佳答案是 atty
- “有没有办法检查标准输入缓冲区是否为空?”
这个答案是针对第二个问题的。我还把它写成一个自包含的 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.*"