我正在尝试从Rust中的GNU -A
复制-B
和grep
参数功能。这会在从文件或stdin读取的匹配行之前和之后打印出文本行。示例:
$ printf '1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9' | grep -A 1 -B 1 foo
3
foo
4
--
--
7
foo
8
我想要的输出将在模式匹配之前和/或之后返回n
行。
仅以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' | cargo run
foo
foo
但是,似乎不可能在这样的迭代器中返回周围的行,因为在每次循环迭代时都无法再访问之前的行。
我看到了Windows
类型,但是它仅适用于切片。
这个sliding_windows
板条箱似乎是我想要的功能,但我无法弄清楚如何使其与文件中的行中的迭代器一起使用。
我也查看了itertools
,但没有看到执行此操作的任何窗口迭代器函数。
在我深入研究自己的形式的行缓冲区对象以便缓存n
先前见过的行(也许是某种环形缓冲区?)之前,我希望可能已经有了某种方法在Rust中可以轻松实现此目的。
答案 0 :(得分:3)
有效地实现这一点非常棘手,而使用滚动缓冲区的本能就很大。这就是GNU grep和ripgrep所做的。如果您愿意产生一些依赖关系,则可以依靠ripgrep的某些内部库来实现所需的功能。例如,这是一个利用grep-searcher
条板箱执行所需操作的程序:
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"
其输出是:
$ printf '1\n2\n3\nfoo\n4\n5\n6\n7\nfoo\n8\n9' | ./target/release/grepex
3
foo
4
--
7
foo
8
如果不需要正则表达式,可以删除grep-regex
依赖项,但是它需要编写更多代码以提供自己的grep-matcher::Matcher
实现(如果看起来不那么容易,您只需要简单的子字符串搜索即可。
如果您确实想自己实现此功能,则可以尝试阅读implementation inside grep-searcher
。实际上,它们全都建立在roll buffer之上。
如果您不太关心性能,则可以逐行循环,并保留足够的缓冲区来存储前N
行(其中N
是您的大小“之前”窗口)。找到匹配项后,从缓冲区中打印前N
行。同时,在N
处启动一个计数器,该计数器每连续一行将减少1
。当计数器在0
上方时,打印对应于“之后”窗口的行。 (这与滚动缓冲区的操作并没有什么不同。)
答案 1 :(得分:1)
一种方法是开发一种状态机,该状态机始终保持最后2N + 1行的窗口。当找到匹配行时,它将新条目添加到与将来的行号关联的待打印的未决匹配列表中。当到达该将来的行号时,将打印具有该行号的条目以及从上下文窗口中拉出的上下文行,请记住,靠近输入开头的匹配项少于N个前导上下文行。当输入结束时,将打印所有仍待处理的条目,请记住这些条目的尾随上下文行少于N。
与所有这些突变抗争Rust!