游客发表
写了个小脚本,专门用来在本地几千个 Markdown 文件里搜关键词。搜索脚本起因很简单:笔记软件自带的搜索脚本搜索太慢,而且老是搜索脚本把代码块里的注释也匹配出来,烦。搜索脚本
脚本不长,搜索脚本一百来行 Python。搜索脚本核心就干一件事:遍历目录,搜索脚本读文件,搜索脚本用正则匹配,搜索脚本打印结果。搜索脚本但真写起来,搜索脚本坑比想象的搜索脚本多。
跑起来才发现,编码问题先炸了。有几个文件是 GBK 存的,Python 默认 utf-8 直接报错。加个 errors='ignore' 能跑,但中文全变问号。后来老老实实用 chardet 猜编码,再 decode,速度掉了一半。
更头疼的是正则。我想搜“函数定义”,写了个 def\s+\w+。结果代码块里的示例、行内代码、甚至注释掉的旧函数全蹦出来了。有次搜 import,返回两百多条,一半是文档里教人写 import 的。这不行。
搜索的本质不是匹配字符,是匹配意图。但脚本不知道你的意图,它只认模式。
于是加了个简单状态机。逐行读,遇到 ``` 就切换 in_code_block 标志,遇到 ` 就跳过行内代码段。只对普通文本行做匹配。代码块里的内容除非你显式加个 --include-code 参数,否则一律忽略。
这招管用。搜“配置”的时候,终于不会把 YAML 示例里的 config: 也捞出来了。但新问题又来了:有些笔记里用 HTML 标签写表格,<td> 里的文字被我的行内代码检测误判。后来把反引号检测改成只认成对出现的,单蹦的不算。代码又多了十几行。
性能方面,纯 Python 遍历确实不快。我试过用 ripgrep 做后端,脚本只负责解析参数和格式化输出。那速度,几千个文件不到一秒。但依赖外部二进制,换台机器就得重新装。纠结了半天,还是保留了纯 Python 实现,加了 multiprocessing 按 CPU 核数分片。现在搜一次大概两秒,能忍。
输出格式也改了好几版。一开始只打印文件名,后来觉得不够,加上行号和匹配行的前后各一行上下文。再后来觉得颜色区分更直观,用 colorama 把关键词标红,文件名标蓝。终端里跑起来,一眼就能扫到重点。
有个细节挺有意思。我本来想加个 --fuzzy 模糊搜索,用 Levenshtein 距离。试了一下,搜“协程”能把“携程”也匹配出来。但笔记里恰好有一篇讲旅游的,里面全是“携程”。结果搜技术笔记搜出一堆机票攻略。果断删了这个功能。精确匹配在个人知识库里比模糊匹配有用得多,至少对我来说是这样。
脚本现在稳定用了大半年。偶尔加个小功能,比如 --exclude-dir 排除附件目录,或者 --ext 指定只搜 .md 和 .org。代码量控制在三百行以内,没写测试,但每个函数都短得一眼能看完。
要说学到了什么。大概是:搜索工具的设计,八成时间花在决定“不搜什么”上。排除代码块、排除行内代码、排除二进制文件、排除 .git 目录。每加一条排除规则,结果就干净一分。剩下的两成,才是匹配逻辑本身。
上周又改了一处。之前遇到超长行(比如压缩过的 JSON)会卡住,因为正则回溯爆炸。现在加了行长度限制,超过 500 字符的行直接跳过,并打印个警告。世界清净了。
脚本还在 GitHub 上,私有仓库。没打算开源,因为写得太随意,变量名都是 tmp、buf、line2 这种。但自己用,够了。
```友情链接