Loops 当迭代文件中的行时,如何在匹配前后输出行?

Loops 当迭代文件中的行时,如何在匹配前后输出行?,loops,rust,Loops,Rust,我正在尝试从Rust中的GNUgrep复制-A和-B参数功能。这将打印从文件或stdin读取的匹配行前后的文本行。例如: $printf'1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9'| grep-a1-b1foo 3. 福 4. -- -- 7. 福 8. 我想要的输出将返回模式匹配之前和/或之后的n行 以just stdin为例,仅返回匹配行的简单情况很容易实现,如下所示: use std::io::{self, BufRead, BufReader, Resul

我正在尝试从Rust中的GNU
grep
复制
-A
-B
参数功能。这将打印从文件或stdin读取的匹配行前后的文本行。例如:

$printf'1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9'| grep-a1-b1foo
3.
福
4.
--
--
7.
福
8.
我想要的输出将返回模式匹配之前和/或之后的
n

以just stdin为例,仅返回匹配行的简单情况很容易实现,如下所示:

use std::io::{self, BufRead, BufReader, Result};
fn main() {
    for line in BufReader::new(io::stdin()).lines() {
        match line {
            Ok(l) => {
                if l.contains("foo"){
                    println!("{}", l);
                }
            }
            Err(e) => println!("error parsing line: {:?}", e),
        }
    }
}
输出:

$printf'1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9'|货物运输
福
福
然而,像这样在迭代器中返回周围的行似乎是不可能的,因为在循环的每次迭代中,前面的行不再是可访问的

我看到了这个类型,但它只在一个切片上起作用

这个板条箱似乎是我想要的那种功能,但我不知道如何让它在文件中的行上使用迭代器

我还查看了窗口迭代器函数,但没有看到任何类型的窗口迭代器函数


在我深入滚动我自己形式的line buffer对象以缓存以前看到的行(可能是某种环形缓冲区?)之前,我希望Rust中已经有一些方法可以轻松实现这一点

一种方法是开发一个状态机,它始终保持最后2N+1行的窗口。当找到匹配行时,它会将一个新条目添加到与未来行号关联的待打印待匹配项列表中。当达到该未来行号时,将打印具有该行号的条目,以及从上下文窗口中提取的上下文行,请记住,接近输入开头的匹配项的前导上下文行少于N行。当到达输入末尾时,将打印所有仍挂起的条目,请记住这些条目的尾随上下文行少于N行


祝你好运,与所有这些变异抗锈

有效地实现这一点是相当棘手的,而您使用滚动缓冲区的本能也非常重要。这就是GNUGREP和ripgrep所做的。如果您愿意产生一些依赖性,那么通过依赖ripgrep的一些内部库,您几乎可以实现您想要的。例如,这里有一个利用板条箱做你想做的事情的程序:

其产出是:

$printf'1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9'./target/release/grepex
3.
福
4.
--
7.
福
8.
如果您不需要正则表达式,则可以删除
grep regex
依赖项,但它需要编写更多的代码来提供您自己的实现(如果您只需要简单的子字符串搜索,这比看起来更容易)

如果您确实想自己实现这一点,那么您可以尝试阅读。实际上,这一切都建立在一个


如果性能不太重要,则可以逐行循环,并保留足够的缓冲区来存储以前的
N
行(其中
N
是“之前”窗口的大小)。找到匹配项后,从缓冲区打印上一行
N
。同时,在
N
处启动一个计数器,该计数器每连续一行减少
1
。当计数器高于
0
时,打印与“after”窗口相对应的行。(这与使用滚动缓冲区没有什么不同。)

感谢您的链接,但“在任意迭代器/集合上使用块和窗口的最佳方法是首先将其收集到Vec中,然后对其进行迭代。”->这正是我试图避免的。我试图在Rust中复制
grep
的确切功能,因此必须将整个文件读取到内存中,这违背了程序的目的。我一次只想在内存中保留
n
+1行;正在迭代的行及其在输入流中的上一行和下一行。继续阅读答案中包含完整的、准备就绪的代码段的部分,该代码段创建了您想要的分块内容。“如果接受动态分配,那么就可以使用
Vec
作为分块迭代器的项。”谢谢,我在那里得到了一个简单的答案,并在匹配后输出该行。它甚至具有与GNU
grep
完全相同的内存占用空间,在3GB文件上迭代时约为900KB,尽管需要4倍的时间(4分钟vs 1分钟)。但是,我不知道如何将其扩展到包括匹配之前的行,可能仍然需要某种缓冲区。是的,这大概就是我为自己的实现所做的工作,基本上是一个
Vec
,它得到
push
,然后在每次迭代中将每一行截断为
n
大小,当找到匹配项时,打印Vec内容。这将适用于比赛前的线路。不过,我还没有弄明白比赛结束后该如何处理台词。我想这可能会对比赛后的台词有所帮助,但我找不到一个有效的例子来说明如何使用它
use std::error::Error;
use std::io;

use grep_regex::RegexMatcher;
use grep_searcher::{Searcher, SearcherBuilder, Sink, SinkContext, SinkMatch};

fn main() -> Result<(), Box<dyn Error>> {
    let re = RegexMatcher::new(r"foo")?;
    let mut searcher = SearcherBuilder::new()
        .before_context(1)
        .after_context(1)
        .build();
    searcher.search_reader(
        &re,
        io::stdin().lock(),
        MySink(io::stdout().lock()),
    )?;
    Ok(())
}

struct MySink<W>(W);

impl<W: io::Write> Sink for MySink<W> {
    type Error = io::Error;

    fn matched(
        &mut self,
        _: &Searcher,
        mat: &SinkMatch,
    ) -> Result<bool, io::Error> {
        self.0.write_all(mat.bytes())?;
        Ok(true)
    }

    fn context(
        &mut self,
        _: &Searcher,
        ctx: &SinkContext,
    ) -> Result<bool, io::Error> {
        self.0.write_all(ctx.bytes())?;
        Ok(true)
    }

    fn context_break(
        &mut self,
        _: &Searcher,
    ) -> Result<bool, io::Error> {
        self.0.write_all(b"--\n")?;
        Ok(true)
    }
}
[dependencies]
grep-regex = "0.1.5"
grep-searcher = "0.1.6"