ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ripgrep 用户指南深度实战:从逐行正则搜索到忽略过滤、类型过滤、替换与二进制文件处理

ripgrep 用户指南深度实战:从逐行正则搜索到忽略过滤、类型过滤、替换与二进制文件处理 ripgrep 用户指南深度实战从逐行正则搜索到忽略过滤、类型过滤、替换与二进制文件处理【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep本文基于 ripgrep 官方用户指南 GUIDE.md 逐章展开覆盖 ripgrep 的核心工作流逐行模式匹配与正则语法、递归目录搜索、自动/手动过滤、输出替换、配置文件、文件编码、二进制文件三态处理以及--pre预处理器机制。读完本文后你不仅能完整掌握 ripgrep 的常用参数与操作手法还能对照 crates/core/search.rs、crates/core/flags/config.rs、crates/ignore/src/types.rs 等源码位置理解每个行为背后的实现依据。基础逐行匹配与正则模式ripgrep 是一个命令行搜索工具它假定自己逐行读取文件若某一行匹配你给定的模式就打印该行不匹配则跳过。这是理解 ripgrep 一切输出格式行号、文件分组、颜色高亮的前提。指南以搜索 ripgrep 自身源码为例。在 README.md 中查找字面词fast$ rg fast README.md 75: faster than both. (N.B. It is not, strictly speaking, a drop-in replacement 88: color and full Unicode support. Unlike GNU grep, ripgrep stays fast while 119:### Is it really faster than everything else? 124:Summarizing, ripgrep is fast because: 129: optimizations to make searching very fast.这里fast是一个字面量literal模式ripgrep 对每一行做包含判断并默认附上行号若终端支持颜色命中部分会被高亮。ripgrep 同时支持完整的正则表达式。想找到fast 后面还跟着若干字母的行$ rg fast\w README.md 75: faster than both. (N.B. It is not, strictly speaking, a drop-in replacement 119:### Is it really faster than everything else?模式fast\w表示fast后跟一个或多个词字符\w匹配a、L这类构词字符不匹配.、空格表示前一模式重复一次或多次。因此裸词fast不匹配faster、faste都匹配。若改用fast\w**表示零次或多次匹配到的行与fast相同但颜色高亮范围会覆盖整个faster而不仅是fast前缀$ rg fast\w* README.md 75: faster than both. (N.B. It is not, strictly speaking, a drop-in replacement 88: color and full Unicode support. Unlike GNU grep, ripgrep stays fast while 119:### Is it really faster than everything else? 124:Summarizing, ripgrep is fast because: 129: optimizations to make searching very fast.指南明确说明不提供完整的正则教程ripgrep 所用的正则方言以regex文档为准其 crate 位于 crates/regex/正则配置入口见 crates/regex/src/config.rs。排查技巧如果 ripgrep 提示没有搜索任何文件用--debug重新运行。指南指出的一个常见原因是$HOME/.gitignore中存在*规则——这与下文自动过滤机制直接相关。递归搜索ripgrep 的默认运行模式指南第一个例子搜索单个文件但 ripgrep 的默认模式是递归搜索当前工作目录因此这几乎不需要额外参数。在解压后的 ripgrep 源码树中查找所有名为write的函数定义$ rg fn write\( src/printer.rs 469: fn write(mut self, buf: [u8]) { termcolor/src/lib.rs 227: fn write(mut self, b: [u8]) - io::Resultusize { 250: fn write(mut self, b: [u8]) - io::Resultusize { 428: fn write(mut self, b: [u8]) - io::Resultusize { self.wtr.write(b) } ...要点未给出路径时rg foo等价于rg foo ./(在正则中有特殊含义所以需要转义为\(也可以直接用-F把模式当字面量rg -F fn write(结果按文件分组打印先文件名后行跨了src与依赖库termcolor两个目录想限定范围直接给出目录rg fn write\( src或cd进目标目录后再搜索。在当前仓库中目录遍历逻辑位于 crates/ignore/src/walk.rs它决定了哪些文件会进入搜索管线。自动过滤ripgrep 最关键的不搜什么指南强调递归搜索之后ripgrep 最重要的特性是它默认不搜索什么。搜索目录时默认忽略命中以下三类 glob 规则的文件与目录按优先级从低到高.gitignore规则包括全局与仓库级规则也包括属于同一 git 仓库的父目录中的.gitignore除非给出--no-require-git.ignore规则与 gitignore 规则冲突时优先生效同样包含父目录中的.ignore.rgignore规则与.ignore冲突时优先生效同样包含父目录中的.rgignore隐藏文件和目录二进制文件ripgrep 把含NUL字节的文件视为二进制符号链接默认不跟随。对应的开关行为标志短写关闭全部忽略过滤--no-ignore—搜索隐藏文件/目录--hidden-.把二进制当文本搜索--text-a注意二进制可能向终端喷出控制字符跟随符号链接--follow-L指南还提供了一个递进式排障标志--unrestricted-u-u关闭.gitignore处理-uu进而搜索隐藏文件与目录-uuu进而搜索二进制文件。当你不确定是过滤规则藏起了结果时多加几个-u是最快的确认手段仍无法解释时再用--debug。gitignore 处理的完整范围除了各级.gitignoreripgrep 还尊重仓库专属规则$GIT_DIR/info/exclude以及core.excludesFile在 Unix 类系统上通常是$XDG_CONFIG_HOME/git/ignore中的全局忽略规则。这些规则的收集逻辑见 crates/ignore/src/gitignore.rs。用.ignore覆写.gitignore假设某目录有如下.gitignorelog/log目录不被 git 跟踪。但你可能希望搜索它的输出又不想让它进 git。在同一目录创建.ignore!log/由于.ignore优先级高于.gitignore.rgignore又高于.ignoreripgrep 会先看到白名单规则!log/并搜索该目录。与.gitignore相同.ignore可以放在任意目录规则相对于所在目录生效。补充两个细节--ignore-file-case-insensitive让.gitignore/.ignore规则按大小写不敏感方式匹配适合 Windows、macOS 这类大小写不敏感的文件系统代价是明显的性能损耗因此默认关闭glob 语义的权威解释参考man gitignore。手动过滤一glob 模式自动过滤依赖环境既有的.gitignore而 glob 过滤是临时的、显式的。仍以上述源码树为例想看谁依赖参数解析器lexopt$ rg lexopt [大量结果] $ rg lexopt -g *.toml Cargo.toml 57:lexopt 0.3.0-g *.toml的含义是被搜索的每个文件都必须匹配这个 glob。注意*.toml要用单引号防止 shell 展开*。glob 同样支持!取反$ rg lexopt -g !*.toml [大量结果但都不以 .toml 结尾]!表示黑名单这一点有点非标准但作者有意与.gitignore的写法保持一致——只是语义方向相反.gitignore中!前缀表示白名单命令行上!表示黑名单。顺序即优先级glob 的解释方式与.gitignore相同后面的规则覆盖前面的$ rg lexopt -g !*.toml -g *.toml # 只搜 *.toml反过来$ rg lexopt -g *.toml -g !*.toml # 什么都不匹配因为只要存在至少一个非黑名单 glob就要求每个被搜索文件至少匹配一个 glob此时黑名单规则压过前面的 glob导致任何文件都无法被搜索。手动过滤二文件类型当你反复使用同一组 glob比如总是只想看 Rust 文件可以直接用文件类型替代 glob$ rg fn run -g *.rs # 等价于 $ rg fn run --type rust # 更简洁 $ rg fn run -trust--type的本质是给一组 glob 起一个名字一个类型可以覆盖多种扩展名。以 C 语言为例用 glob 需要写-g *.{c,h}用类型只需-tc$ rg int main -g *.{c,h} $ rg int main -tc黑名单文件类型同理$ rg lexopt --type-not rust $ rg lexopt -Trust # -T --type-not即-t是包含该类型-T是排除该类型。查看某类型由哪些 glob 构成$ rg --type-list | rg ^make: make: *.mak, *.mk, GNUmakefile, Gnumakefile, Makefile, gnumakefile, makefile内置类型覆盖常见的公开格式定义集中在 crates/ignore/src/default_types.rs你也可以自定义。例如把web文件定义为 HTML/CSS/JS$ rg --type-add web:*.html --type-add web:*.css --type-add web:*.js -tweb title # 或更简洁 $ rg --type-add web:*.{html,css,js} -tweb title再次rg --type-add web:*.{html,css,js} --type-list时web会出现在列表中尽管它不是内置类型。重要--type-add只对当前命令生效不会被持久化。要全局可用要么建 shell 别名alias rgrg --type-add web:*.{html,css,js}要么把--type-addweb:*.{html,css,js}写进 ripgrep 配置文件见下文配置文件一节。从源码看类型名还受到严格约束crates/ignore/src/types.rs 中TypesBuilder::add要求类型名只能是字母数字且不能占用alladd_defL442-L479还额外支持{name}:include:{已有类型列表}的复合定义形式即一个类型可以由若干已有类型组合而成——这一点指南没有展开属于源码层面的加分能力。特殊的all类型--type all表示选择--type-list中列出的所有支持类型包括命令行--type-add添加的。等价于为每个内置类型各写一个--type标志。指南给了一个很好的边界例子当前目录有my-shell-script无扩展名脚本和my-shell-library.bashrg --type sh与rg --type all都只命中my-shell-library.bash因为sh类型的 glob 不匹配无扩展名文件反过来rg --type-not all会搜索my-shell-script但不搜my-shell-library.bash。源码中all的特殊处理见 crates/ignore/src/types.rs#L381-L404select/negate遇到名字all时会遍历当前已定义的全部类型逐个添加选择/反选择。替换--replace只改输出不改文件ripgrep 提供有限的输出改写能力。仍以rg fast README.md为例把命中片段替换为FAST$ rg fast README.md --replace FAST 75: FASTer than both. (N.B. It is not, strictly speaking, a drop-in replacement 88: color and full Unicode support. Unlike GNU grep, ripgrep stays FAST while 119:### Is it really FASTer than everything else? 124:Summarizing, ripgrep is FAST because: 129: optimizations to make searching very FAST. # 或简写 $ rg fast README.md -r FAST--replace只作用于匹配到的那部分文本。想替换整行需要让模式覆盖整行$ rg ^.*fast.*$ README.md -r FAST 75:FAST 88:FAST 119:FAST 124:FAST 129:FAST或者组合--only-matching-o与--replace$ rg fast README.md --only-matching --replace FAST 75:FAST 88:FAST 119:FAST 124:FAST 129:FAST # 简写 $ rg fast README.md -or FAST捕获组可以直接写进替换串。找fast后面跟的另一个词并连字符拼接$ rg fast\s(\w) README.md -r fast-$1 88: color and full Unicode support. Unlike GNU grep, ripgrep stays fast-while 124:Summarizing, ripgrep is fast-because:替换串fast-$1由字面fast-加第 1 号捕获组内容组成。捕获组从 0 开始编号但第 0 组恒为整个匹配第 1 组才是模式中第一个显式括号组。也可以用命名组下面的命令与上例等价$ rg fast\s(?Pword\w) README.md -r fast-$word 88: color and full Unicode support. Unlike GNU grep, ripgrep stays fast-while 124:Summarizing, ripgrep is fast-because:替换串中捕获组的展开实现位于 crates/matcher/src/interpolate.rs。必须牢记ripgrep 从不修改你的文件--replace只控制输出仓库中也没有任何就地替换标志。配置文件RIPGREP_CONFIG_PATH与 rc 文件格式默认参数并不总合适而 shell 别名也不总是方便因此 ripgrep 支持配置文件。它不会自动去任何目录找配置文件必须显式设置环境变量export RIPGREP_CONFIG_PATH$HOME/.ripgreprc配置文件只有两条格式规则每行去掉首尾空白后作为一个 shell 参数以#开头的行前面可带任意空白被忽略。没有转义机制——每行按原样作为一个命令行参数交给 ripgrep。一份示例配置展示了格式的种种特性$ cat $HOME/.ripgreprc # 不让 ripgrep 把超长行喷满终端并显示预览 --max-columns150 --max-columns-preview # 添加我的 web 类型 --type-add web:*.{html,css,js}* # 默认搜索隐藏文件/目录 --hidden # 用 glob 模式包含/排除文件或目录 --glob!.git/* # 或 --glob !.git/* # 设置颜色 --colorsline:none --colorsline:style:bold # 大小写谁在乎 --smart-case带值的标志有两种等价写法同一条分隔--max-columns150或标志与值分两行写。原因是 ripgrep 的参数解析器认识--max-columns150这种带值单参数而写--max-columns 150两词时它无法确定关系。分两行完全等价只是风格问题。注释鼓励常写空行随意。覆盖机制假如你在用上面的配置临时想看超过 150 列的长行只需在命令行传--max-columns 0或-M0覆盖即可。原理是配置文件参数被前插prepended到显式命令行参数之前而后出现的标志覆盖先出现的标志因此行为符合直觉。各标志的文档会说明它会被哪些标志覆盖。两个实用收尾不确定 ripgrep 正在读哪个配置文件时加--debug调试输出会标注加载的配置及其读到的参数想绝对确认没有读取任何环境配置传--no-config无论未来 ripgrep 增加多少种配置方式它都保证只信命令行。源码印证crates/core/flags/config.rs 中args()首先读取RIPGREP_CONFIG_PATH未设置时直接返回空参数并记录调试日志parse_readerL84-L108逐行trim后跳过空行与#行每行转为OsString原样保留——这正对应文档描述的无转义、每行一个参数。文件内的单元测试还验证了 Unix 下可容忍非 UTF-8 字节、而 Windows 下会按行报错的平台差异。文件编码--encoding auto的默认行为编码本身是复杂话题指南将其对 ripgrep 的要点归纳为文件只是一堆字节无法可靠地判断其编码要么模式与文件编码一致要么必须对模式或文件做转码ripgrep 在纯文本上表现最好最常见的编码是 ASCII、latin1、UTF-8特例是 Windows 环境中普遍存在的 UTF-16。默认即--encoding auto其行为假设所有输入 ASCII 兼容凡是落在 ASCII 码点范围内的字节其值就是该 ASCII 码点这覆盖 ASCII、latin1、UTF-8ripgrep 对 UTF-8 支持最好正则引擎支持 Unicode 特性\w按 Unicode 定义匹配所有词字符.匹配任意 Unicode 码点而非任意字节。这些构造都假定 UTF-8——遇到文件里的非 UTF-8 字节时它们根本不会匹配对 UTF-16ripgrep 默认做BOM 嗅探读取文件前三字节若是 UTF-16 BOM则把文件内容从 UTF-16 转码为 UTF-8 再搜索转码带来额外性能开销遇到无效 UTF-16 时用 Unicode 替换码点顶替无效码元其他编码用-E/--encoding指定取值来自 Encoding Standardripgrep 假定所有被搜索文件除非文件自带 BOM都是该编码并执行与 UTF-16 情况相同的转码步骤。默认情况下 ripgrep 不要求输入是合法 UTF-8它可以直接搜索任意字节。搜索非 UTF-8 内容时模式的有效性会下降若文件字节不 ASCII 兼容模式很可能什么都找不到。但这一模式很重要——它让你在大体是二进制/乱码的文件中找出其中的 ASCII 或 UTF-8 片段。-E none是特殊值完全禁用一切编码逻辑包括 BOM 嗅探直接搜索文件原始字节、零转码。例如搜索字符串Шерлок的原始 UTF-16 编码$ rg (?-u)\(\x045\x04\x04;\x04\x04:\x04 -E none -a some-utf16-file当然通常你不需要这么干直接写原文即可$ rg Шерлок some-utf16-file最后在正则内部关闭 Unicode若想让.匹配任意字节而非任意 Unicode 码点比如搜二进制文件时因为默认的.不匹配无效 UTF-8$ rg (?-u:.)该开关作用于模式任意部分。下面这个例子找一个 Unicode 词字符 一个 ASCII 词字符 一个 Unicode 词字符$ rg \w(?-u:\w)\w二进制数据三种模式的 NUL 启发式除了隐藏文件与.gitignore规则ripgrep 还默认跳过二进制文件——PDF、图片之类通常不是正则搜索目标而且命中二进制内容时把二进制数据喷进终端可能引发各种怪事。与跳过隐藏文件不同二进制没有可靠判定法。权衡正确性与性能后ripgrep 采用最简单有效的启发式文件中只要含一个NUL字节就判定为二进制。麻烦在于大多数二进制文件只是开头附近就有NUL并非必然——NUL也可能是大文件的最后一个字节该文件仍算二进制。这给实现带来复杂度也造成一些反直觉的用户体验。宏观上 ripgrep 对二进制文件有三种模式默认模式尽量把二进制文件从搜索中彻底移除模仿自动过滤的语义——一旦发现是二进制就停止搜索。若在此之前已经打印过命中因为NUL出现得很晚会打印一条搜索提前终止的警告。该模式只作用于目录递归遍历发现的文件显式给出的路径不受此约束例如rg foo .file会搜隐藏的.filerg foo binary-file也会自动以二进制模式搜索binary-file。二进制模式--binary强制开启与默认模式相似但看到NUL后不总是立即停。它会继续搜到满足以下二者之一为止文件末尾或发现了一个匹配。因此该模式下报告无匹配意味着文件里确实没有匹配命中时会打印类似默认模式的提前终止提示。目的是既能发现所有文件中的匹配又不让二进制数据倒进终端。文本模式-a/--text彻底禁用二进制检测所有文件都当文本搜。适合大体是文本但含NUL的文件或你就是想搜二进制数据。注意对超大二进制文件使用此模式时ripgrep 可能占用大量内存。还有一层实现细节会影响判定结果——检测范围取决于搜索策略使用内存映射mmap时只在文件开头若干 KB 及每个命中行上做二进制检测不使用 mmap 时对所有被搜索字节做检测。也就是说同一文件是否被判为二进制可能因内部策略不同而变化。想保持判定一致可用--no-mmap关闭内存映射代价是在某些平台上搜索超大文件时轻微变慢。源码层面检测策略枚举在 crates/searcher/src/line_buffer.rs 的BinaryDetectionNone/Quit(byte)/Convert(byte)三态crates/core/search.rs 中每次搜索前都会按配置set_binary_detection随后在search_reader/search_path中执行。预处理器--pre让 ripgrep 会搜任何可转成文本的格式在 ripgrep 中预处理器preprocessor是一个外部命令ripgrep 在搜索每个文件之前先用它转换输入。这让 ripgrep 无需学会某种格式就能搜索任何能被自动转成文本的内容。典型例子是搜 PDF。PDF 是二进制格式页面上的文字未必是连续 UTF-8所以即使加-a/--text也搜不到$ rg The Commentz-Walter algorithm 1995-watson.pdf $可以先手动转文本再搜$ pdftotext 1995-watson.pdf 1995-watson.txt $ rg The Commentz-Walter algorithm 1995-watson.txt 316:The Commentz-Walter algorithms : : : : : : : : : : : : : : : 7165:4.4 The Commentz-Walter algorithms 10062:in input string S , we obtain the Boyer-Moore algorithm. The Commentz-Walter algorithm ...pdftotext属于 poppler 库。但目录里全是 PDF 时手动转换太痛苦这时用--pre。--pre接收一个命令名对每个被搜索文件执行它ripgrep 把文件路径作为唯一参数传给命令同时把文件内容送入 stdin。据此写一个包装脚本$ cat preprocess #!/bin/sh exec pdftotext - -把preprocess与1995-watson.pdf放在同目录后即可$ rg --pre ./preprocess The Commentz-Walter algorithm 1995-watson.pdf 316:The Commentz-Walter algorithms : : : : : : : : : : : : : : : 7165:4.4 The Commentz-Walter algorithms 10062:in input string S , we obtain the Boyer-Moore algorithm. The Commentz-Walter algorithm ...注意preprocess必须能解析为 ripgrep 可读的命令最简做法是放进PATH或等效机制里或使用绝对路径。指南还给出对比数据同一 PDF 上rg --pre ./preprocess ... -c耗时 0.697 秒而pdfgrep ... -c耗时 1.336 秒如果批量搜 PDFripgrep 的并行能力会进一步放大差距。源码印证这一接口契约crates/core/search.rs 的search_preprocessor中cmd.arg(path)传入路径、stdin(Stdio::from(File::open(path)?))把文件内容送入 stdin与文档描述完全一致should_preprocessL284-L292则说明设置了预处理器但未给 glob 时每个文件都会走预处理器。更健壮的预处理器上面的脚本对非 PDF 文件会失败$ echo foo not-a-pdf $ rg --pre ./preprocess The Commentz-Walter algorithm not-a-pdf not-a-pdf: preprocessor command failed: ./preprocess not-a-pdf: ------------------------------------------------------------------------------- Syntax Warning: May not be a PDF file (continuing anyway) Syntax Error: Couldnt find trailer dictionary ...修复方法只在认为输入是非空 PDF时才跑pdftotext#!/bin/sh case $1 in *.pdf) # -s 保证文件非空 if [ -s $1 ]; then exec pdftotext - - else exec cat fi ;; *) exec cat ;; esac还可以扩展到其他格式文件名不能确定类型时用file工具按内容嗅探#!/bin/sh case $1 in *.pdf) if [ -s $1 ]; then exec pdftotext - - else exec cat fi ;; *) case $(file $1) in *Zstandard*) exec pzstd -cdq ;; *) exec cat ;; esac ;; esac降低预处理器开销每个文件都启动一次预处理器进程开销不小。若只有少数文件需要预处理可用--pre-glob限定只对匹配 glob 的路径启用$ time rg --pre pre-rg fn is_empty -c crates/globset/src/lib.rs:1 crates/matcher/src/lib.rs:2 crates/ignore/src/overrides.rs:1 crates/ignore/src/gitignore.rs:1 crates/ignore/src/types.rs:1 real 0.138 $ time rg --pre pre-rg --pre-glob *.pdf fn is_empty -c crates/globset/src/lib.rs:1 crates/ignore/src/types.rs:1 crates/ignore/src/gitignore.rs:1 crates/ignore/src/overrides.rs:1 crates/matcher/src/lib.rs:2 real 0.008同样搜索 ripgrep 仓库加--pre-glob *.pdf后从 0.138 秒降到 0.008 秒——因为几乎所有文件都不需要再启动预处理器子进程。--pre-glob的匹配逻辑即前文源码中的preprocessor_globsignore::overrides::Override。常用选项速查ripgrep 的标志多到记不住指南选取了日常使用频率最高的一组-h简版帮助--help完整版帮助接近 man page 内容建议管道进分页器-i/--ignore-case忽略大小写rg -i fast同时匹配fast、fASt、FAST-S/--smart-case类似--ignore-case但模式含大写字母时自动关闭忽略大小写。通常放进别名或配置文件-F/--fixed-strings关闭正则模式按字面量处理-w/--word-regexp要求命中两侧都是词边界。相当于把模式包成\b{start-half}(?:pattern)\b{end-half}。注意这些半边界不要求一侧是词字符rg -w -e -2能匹配(-2)中的-2而rg \b-2\b不能-c/--count只报告命中行数总和--files打印 ripgrep将要搜索的文件但不实际搜索——排查过滤问题的利器-a/--text把二进制当纯文本搜索-U/--multiline允许匹配跨多行-z/--search-zip搜索压缩文件gzip、bzip2、lzma、xz、lz4、brotli、zstd默认关闭-C/--context显示命中周围的上下文行--sort path按文件名排序输出会关闭并行可能更慢-L/--follow递归搜索时跟随符号链接-M/--max-columns限制打印行长度--debug打印调试输出用于理解某个文件为何被忽略、以及 ripgrep 从环境加载了什么配置。小结ripgrep 的使用心法可以浓缩为三层逐行正则匹配是基础含?Pname命名组、(?-u:...)按片段关闭 Unicode 等正则细节过滤决定搜什么.gitignore/.ignore/.rgignore优先级链、-gglob、-t/-T类型与特殊的all模式与接口处理特殊输入--encoding/BOM 嗅探、NUL 三态二进制检测、--pre/--pre-glob预处理器、--replace只改输出。遇到问题时-u阶梯与--debug是排障的第一选择RIPGREP_CONFIG_PATH配置文件 --no-config则是参数长期化管理的开关。以上每个行为在当前仓库中都能找到对应的实现位置忽略规则收集在 crates/ignore/二进制检测与搜索策略在 crates/searcher/预处理器与压缩解包在 crates/core/search.rs配置文件解析在 crates/core/flags/config.rs。【免费下载链接】ripgrepripgrep recursively searches directories for a regex pattern while respecting your gitignore项目地址: https://gitcode.com/GitHub_Trending/ri/ripgrep创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表