首页
/ ripgrep 的 grep 库 crate:用 Rust 库组装一个高性能行式正则搜索器

ripgrep 的 grep 库 crate:用 Rust 库组装一个高性能行式正则搜索器

2026-09-04 09:05:05作者:曹令琨Iris

grep crate 是 ripgrep 的“库门面”:它把 ripgrep 核心搜索管线拆成的各子 crate(matcher、searcher、printer 等)重新导出为一个统一的依赖,让第三方项目可以像调用 ripgrep 一样调用其搜索能力。本文以 crates/grep/README.md 为主线,结合仓库源码讲解该 crate 的依赖引入、三大核心组件(Matcher / Searcher / Sink)的拼装方式、自带的 simplegrep 示例程序,以及 pcre2 可选 feature 的实现与在主程序中的落地,最终给出一套可直接复用的“库级搜索器”搭建方案。

1. crate 定位:ripgrep, as a library

READMEgrep crate 的定位只有一句话:“ripgrep, as a library”(ripgrep,作为库)。它自身几乎不含逻辑代码,真正的实现全部来自其重新导出的子 crate。这一点在 crates/grep/src/lib.rs 中一目了然:

pub extern crate grep_cli as cli;
pub extern crate grep_matcher as matcher;
#[cfg(feature = "pcre2")]
pub extern crate grep_pcre2 as pcre2;
pub extern crate grep_printer as printer;
pub extern crate grep_regex as regex;
pub extern crate grep_searcher as searcher;

也就是说,用户 use grep::{...} 时拿到的其实是六个组件:

重导出名 对应子 crate 仓库路径 职责
grep::cli grep-cli crates/cli 命令行参数解析、文件路径/编码处理等 CLI 辅助逻辑
grep::matcher grep-matcher crates/matcher 定义 Matcher trait——所有模式匹配引擎的统一接口
grep::regex grep-regex crates/regex 基于 Rust regex crate 的 Matcher 默认实现
grep::pcre2 grep-pcre2(可选) crates/pcre2 基于 PCRE2 的 Matcher 替代实现
grep::searcher grep-searcher crates/searcher 行式搜索引擎:读取字节流、执行匹配、报告结果
grep::printer grep-printer crates/printer 将匹配结果输出为 grep 风格、JSON Lines 或统计摘要

这种“薄门面 + 厚子 crate”的组织方式意味着:既可以依赖整个 grep crate 获得全家桶,也可以只依赖 grep-searchergrep-matcher 等单个子 crate 做精细裁剪。

README 中还有一段重要的成熟度声明(lib.rs 模块文档中也有同样表述):该 crate 尚未为“广泛使用”做好准备——每个公开 API 项都有文档,但缺少把所有部件拼装起来的高层指南,示例也比较稀疏。换言之,它适合“有野心、愿意读子 crate 文档”的使用者,而不适合开箱即用。本文后面的拼装流程正是为了补齐这块空白。

2. 引入依赖

README 给出的用法是在 Cargo.toml 中添加:

[dependencies]
grep = "0.2"

需要注意两处事实:

  • 当前仓库中 crates/grep/Cargo.toml 声明的版本已经是 0.4.1,README 示例中的 0.2 是历史写法;实际接入时以 crates.io 上的最新版本号为准,grep = "0.4" 之类的 semver 约束均可覆盖。
  • 该 crate 双许可(MIT / Unlicense),与 ripgrep 主程序一致,商用无负担。

grep crate 的依赖清单(见 crates/grep/Cargo.toml)即上表六个子 crate 的对应版本;此外 termcolorwalkdir 只是 dev-dependencies,专供示例程序编译使用,不会传染给你的项目。

3. 三大核心组件:Matcher、Searcher、Sink

理解 grep crate 的关键是理解搜索管线的三个角色,它们在 crates/searcher/src/lib.rs 的模块文档中有一段权威描述:

  • Matcher(匹配器):定义于 crates/matcher/src/lib.rs,是“最低层模式搜索”的抽象接口。它刻意选择了内部迭代(push 模型):由匹配实现驱动搜索,找到匹配时回调调用方传入的闭包。文档给出的理由是:某些搜索实现(如带回溯的 PCRE)本身就只能内部迭代,而 Rust 的类型系统用外部迭代写通用接口会牺牲易用性或性能。grep-regex(默认)与 grep-pcre2(可选)都是 Matcher trait 的实现。
  • Searcher(搜索器):由 SearcherBuilder 配置构建,负责从数据源读取字节、用 Matcher 执行搜索、把结果上报给 Sink。它还顺带负责反转搜索、行计数、上下文行、二进制检测、是否使用内存映射(mmap)等决策。
  • Sink(接收器):描述调用方如何接收搜索结果——搜索开始/结束、匹配行、上下文行都会回调到 Sinkgrep-printerStandard 打印器就是“效果上实现了 grep 风格输出”的复杂 Sink;子 crate 也在 sinks 子模块里提供了基于闭包的轻量 Sink(如 UTF8)。

三者的协作关系可以概括为一句话:Searcher 消费 Matcher,并把结果推给 Sinkgrep crate 的价值就在于把这三类组件打包成一次依赖。

3.1 最小搜索示例(searcher 文档自带)

以下示例直接取自 crates/searcher/src/lib.rs 的模块文档,展示“匹配器 + 内存切片 + 闭包 Sink”的最短路径:

use {
    grep_matcher::Matcher,
    grep_regex::RegexMatcher,
    grep_searcher::Searcher,
    grep_searcher::sinks::UTF8,
};

let matcher = RegexMatcher::new(r"Doctor \w+")?;
let mut matches: Vec<(u64, String)> = vec![];
Searcher::new().search_slice(&matcher, SHERLOCK, UTF8(|lnum, line| {
    // 每行必然有匹配,所以 unwrap 可以接受
    let mymatch = matcher.find(line.as_bytes())?.unwrap();
    matches.push((lnum, line[mymatch].to_string()));
    Ok(true)
}))?;

注意 Match 类型(crates/matcher/src/lib.rs)结构上等价于 Range<usize>,可直接用 line[mymatch] 切片取匹配子串,并且保证 start <= end

4. 完整示例:simplegrep 源码拆解

仓库自带一个可运行的最小命令行搜索器 crates/grep/examples/simplegrep.rs,它只用了约 70 行就实现了“simplegrep <pattern> [<path> ...]”的递归目录搜索。这正是 README 所说“piece together the parts”(把部件拼起来)的参考答案。核心逻辑如下:

use {
    grep::{
        cli,
        printer::{ColorSpecs, StandardBuilder},
        regex::RegexMatcher,
        searcher::{BinaryDetection, SearcherBuilder},
    },
    termcolor::ColorChoice,
    walkdir::WalkDir,
};

fn search(pattern: &str, paths: &[OsString]) -> Result<(), Box<dyn Error>> {
    // 1) 构建 Matcher:行匹配器,编译失败即报错退出
    let matcher = RegexMatcher::new_line_matcher(&pattern)?;

    // 2) 构建 Searcher:遇到 NUL 字节判定为二进制并停止,不显示行号
    let mut searcher = SearcherBuilder::new()
        .binary_detection(BinaryDetection::quit(b'\x00'))
        .line_number(false)
        .build();

    // 3) 构建 Printer:终端输出时自动启用颜色
    let mut printer = StandardBuilder::new()
        .color_specs(ColorSpecs::default_with_color())
        .build(cli::stdout(if std::io::stdout().is_terminal() {
            ColorChoice::Auto
        } else {
            ColorChoice::Never
        }));

    // 4) 遍历目录,逐文件执行搜索
    for path in paths {
        for result in WalkDir::new(path) {
            let dent = match result {
                Ok(dent) => dent,
                Err(err) => { eprintln!("{}", err); continue; }
            };
            if !dent.file_type().is_file() {
                continue;
            }
            let result = searcher.search_path(
                &matcher,
                dent.path(),
                printer.sink_with_path(&matcher, dent.path()),
            );
            if let Err(err) = result {
                eprintln!("{}: {}", dent.path().display(), err);
            }
        }
    }
    Ok(())
}

从源码结构看,这个示例覆盖了 grep 门面的五块拼图,且每块都有清晰的职责:

  1. 模式入口cli::pattern_from_os(来自 grep-cli)负责把操作系统字符串规范化为模式文本,这是 ripgrep 主程序处理“首参是模式”的同一套工具。
  2. 匹配引擎RegexMatcher::new_line_matcher 来自 grep-regex,默认引擎即 Rust 的 regex crate。
  3. 搜索策略BinaryDetection::quit(b'\x00') 表示一旦读到 NUL 字节就中止该文件(与 ripgrep 的二进制文件处理一致);line_number(false) 则说明该示例不输出行号前缀——想要 123:内容 这种 grep 风格输出,把它改为 true 即可。
  4. 输出格式化StandardBuilder 生成的 Standard 打印器是 grep 风格的 Sink 实现,sink_with_path 会把文件路径一并纳入输出(多文件搜索时前缀 path:行号:内容)。
  5. 目录遍历:示例用第三方 walkdir 遍历目录,刻意没有引入 .gitignore 感知——这正是 simplegrep 与完整 ripgrep 的差距。若需要尊重 gitignore,从源码结构看应改用 ripgrep 独立的 ignore crate 的 WalkBuilder(主程序正是这样做的,见 crates/core/src/main.rsargs.walk_builder() 的用法)。

构建并运行该示例的命令(在仓库根目录下):

cargo run --example simplegrep -- "your-regex" ./

5. pcre2 feature:可选的第二引擎

README 的 Features 一节指出:grep crate 提供一个默认关闭的 pcre2 feature,启用后会重新导出 grep-pcre2 crate,作为标准 grep-regex 实现之外的另一个 Matcher 实现

5.1 依赖与 feature 声明

crates/grep/Cargo.toml 中的声明印证了这一点:

[dependencies]
# ...
grep-pcre2 = { version = "0.1.9", path = "../pcre2", optional = true }

[features]
pcre2 = ["grep-pcre2"]

使用时即:

[dependencies]
grep = { version = "0.4", features = ["pcre2"] }

之后 grep::pcre2::RegexMatcherBuilder 就会出现在 crates/grep/src/lib.rs#[cfg(feature = "pcre2")] pub extern crate grep_pcre2 as pcre2; 之后,可被正常导入。

另外,该 Cargo.toml 里还保留了两个已废弃的空 feature(simd-accelavx-accel),注释明确说明“现在改用运行时 SIMD 分派”,留空仅为兼容旧依赖声明。

5.2 grep-pcre2 的实现要点

crates/pcre2/src/matcher.rs 中的 RegexMatcherBuilder 展示了 PCRE2 实现如何适配 Matcher 抽象:

  • build_many 把多个模式包成 (?:pat1)|(?:pat2) 的 OR 组合;fixed_strings 模式下会对每个模式做 pcre2::escape,实现字面量固定字符串搜索;
  • case_smart(对应 ripgrep 的 -i“大小写智能”)通过 has_uppercase_literal 检测模式内是否含大写字符,若不含则对整个编译结果开启 caseless
  • wordwhole_line(对应 -w)分别用 PCRE 的 lookaround 改写模式:(?<!\w)(?:pat)(?!\w)(?m:^^) 包裹,其中 whole_lineword 互斥(整行匹配必然落在单词边界上,lookaround 属于冗余);
  • 编译完成后会建立“捕获组名 → 索引”的 HashMap,使 $name 形式的插值也能工作。

5.3 ripgrep 主程序如何消费它

主程序对 grep::pcre2 的使用点恰好证明“门面 → 子 crate”的调用链:

  • crates/core/src/flags/hiargs.rslet mut builder = grep::pcre2::RegexMatcherBuilder::new();——高层参数对象构建 PCRE2 匹配器时走的就是 grep 门面;
  • crates/core/src/search.rs:匹配器枚举中存在 PCRE2(grep::pcre2::RegexMatcher) 变体,运行时由引擎选择决定用哪条实现;
  • crates/core/src/flags/defs.rs--engine 参数接受 default / pcre2 / auto 三个取值,文档注释中写明“PCRE2 引擎是 ripgrep 的可选 feature,未启用时不可用”——这与库侧 pcre2 feature 默认关闭的设计完全对应。

6. 适用边界与注意事项

综合 README 与源码,使用该库 crate 时应明确以下前提:

  1. 成熟度:README 与 lib.rs 文档都声明没有高层使用指南,本文第 3、4 节的拼装流程即为目前最完整的可执行参考;API 在各子 crate 间仍在演进,锁定依赖版本是稳妥做法。
  2. 它不做文件过滤grep 门面不包含 .gitignore/glob/类型过滤能力(这部分在独立的 ignore crate,且主程序并未通过 grep 门面暴露它)。要复刻完整 ripgrep 行为,需要自行组合 ignore
  3. 引擎是可插拔的:默认 grep-regex 引擎开箱即用;需要 lookaround、递归量词等 PCRE2 特性时,开启 pcre2 feature 并切换为 grep::pcre2::RegexMatcher,两者对 Searcher/Sink 而言都是同一个 Matcher trait,管线代码无需改动——这是 Matcher 抽象存在的意义。
  4. 版本差异:README 的 grep = "0.2" 是历史版本,仓库当前为 0.4.1;引用本文代码示例时请以当前仓库源码为准。

小结

crates/grep 这个 README 很短,但它指向的是一套完整的库化架构:一次依赖引入(grep),六个子 crate 重导出,Matcher(grep-regex / grep-pcre2)+ Searcher + Sink(Standard / JSON / Summary)三者拼装即可得到一个具备二进制检测、颜色输出、PCRE2 可选引擎的行式搜索器。crates/grep/examples/simplegrep.rs 是仓库给出的标准拼装示范,而 ripgrep 主程序自身(grep::pcre2::RegexMatcherBuilder 的调用点)则证明了这条“门面 → 子 crate”调用链在真实生产代码中同样成立。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341