从标准输入读取包括 \n 的完整行,直到文件末尾。

5

我希望将这段Python代码转换成Rust:

for line in sys.stdin:
   do something to the whole line including \n

但是我只能找到读取单个完整行(包括 \n)的示例,或者不读取 \n 的示例。

看起来这应该是世界上最简单的事情之一,但我找不到它。


1
澄清一下:您想从stdin中读取所有可用的行,直到没有更多可用为止,对吗? - tobias_k
这个答案是否解决了你的问题?(实际上问题是询问如何读取单行,但是顶部回答展示了如何读取所有行。) - tobias_k
“我只能找到读取单个完整行(包括 \n)的示例” - 那这不是简单地将其放入循环中吗? - kmdreko
@tobias_k 最佳答案使用 lines(),它不会包括终止符\n - user4815162342
@tobias_k 正确。你链接的答案会吃掉 \n。我不想让它吃掉 \n。除此之外,这个答案很好(顺便说一下,这是我找到的答案之一)。 - Ole Tange
@kmdreko 这个读取单行的示例并没有展示如何判断 eof() 是否被设置。我认为还有一种更简单的方式(例如 Perl 的 while(<>) 或 Python 的 for i in sys.stdin)。 - Ole Tange
1个回答

8

无论是Python还是Rust,都提供了方便的API来迭代文件中的行,它们只是在方便性与完整性之间做出了不同的权衡。Rust选择了额外的便利,并剥离了换行符,以便能够区分最后一行是否以终止符结束。Python则选择了相反的方式,以所有行解析器必须考虑最后的\n为代价,同时还必须考虑它是可选的

但是BufRead::lines()只是一个方便的API;如果它不能满足您的需求,您可以随时降到更低级别的read_line()方法:

let mut line = String::new();
while input.read_line(&mut line)? != 0 {
    let line = std::mem::take(&mut line);
    // ...
}

如果您在多个地方使用此类代码,或者只是想要一个for循环的便利性,您可以将其抽象为返回迭代器的实用函数,例如:

fn full_lines(mut input: impl BufRead) -> impl Iterator<Item = io::Result<String>> {
    std::iter::from_fn(move || {
        let mut vec = String::new();
        match input.read_line(&mut vec) {
            Ok(0) => None,
            Ok(_) => Some(Ok(vec)),
            Err(e) => Some(Err(e)),
        }
    })
}

然后你可以使用类似于Python中的for循环:

for line in full_lines(io::stdin().lock()) {
    let line = line?;
    // ...
}

通过额外的努力,甚至可以使full_lines成为实现了BufRead的任何内容的方法:

trait FullLines: BufRead + Sized {
    fn full_lines<'a>(self) -> Box<dyn Iterator<Item = io::Result<String>> + 'a>
    where
        Self: 'a,
    {
        Box::new(full_lines(self))
    }
}

// Provide a blanket implementation of FullLines for any T
// that implements BufRead
impl<T: BufRead> FullLines for T {}

// Usage:

use FullLines;

for line in io::stdin().lock().full_lines() {
    let line = line?;
    // ...
}

不错的回答。但有一件事我不太明白:为什么你要使用 "".to_string() 来创建缓冲区,而不是直接使用 String::new() 呢? - harmic
由于read_line返回读取的字节数,因此您还可以将while条件替换为input.read_line(&mut line)? > 0。聪明地使用了take,我从未想过这样使用它。 - trent
1
@trentcl 谢谢,我已经按照建议简化了它。更复杂的公式仍然来自于我最初尝试使 while 条件也立即返回新行的实验,这样用户就不必在每次循环迭代结束时记得重置它,类似于这样。使用 mem::take() 的版本要简单得多,但是不必要的测试仍然来自于一个版本,其中我会丢弃 read_line 返回值。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接