推荐用 bufio.scanner 流式逐行读取,配合 strings.contains 快速子串匹配或复用 regexp.regexp 实例做模糊匹配;需设 scanner.buffer 支持超长行,避免全文件加载导致 oom。

用 bufio.Scanner 逐行读取并匹配正则或子串
Go 没有内置 grep 命令,但实现核心搜索逻辑非常轻量。关键不是“全文件加载”,而是流式处理:用 bufio.Scanner 一行行读,配合 strings.Contains 或 regexp.MatchString 判断。这样即使 GB 级日志也能低内存运行。
常见错误是直接用 ioutil.ReadFile(已弃用)或 os.ReadFile 把整个文件读进内存——大文件会 OOM,且无法提前退出。
- 对简单子串搜索,优先用
strings.Contains(line, keyword),比正则快 3–5 倍 - 需要模糊匹配(如
error.*timeout)时,复用已编译的*regexp.Regexp实例,避免在循环里反复调用regexp.Compile -
Scanner默认单行上限是 64KB,超长行会被截断;若需支持超长行,调用scanner.Buffer(make([]byte, 64*1024), 1 扩容
带行号和文件名输出时注意 Scanner 的位置偏移
很多人发现打印的“第 N 行”和实际文件行号对不上,根源在于 Scanner 默认跳过空行、且不感知 Windows 的 \r\n 换行符差异。它统计的是成功扫描到的非空行数,不是原始字节流中的换行符个数。
要严格对齐编辑器显示的行号,必须自己计数:
lineNum := 0
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lineNum++
line := scanner.Text()
if strings.Contains(line, keyword) {
fmt.Printf("%s:%d: %s\n", filename, lineNum, line)
}
}
- 不要依赖
scanner.Bytes()后手动解析换行符——Scan()已剥离换行符,再解析易出错 - 如果文件含 BOM(如 UTF-8 with BOM),
scanner.Text()开头可能有\ufeff,影响字符串匹配,建议用bytes.TrimPrefix预处理 - Windows 下用
\r\n换行时,scanner.Text()自动去除,无需额外处理
递归搜索目录需用 filepath.WalkDir 而非 filepath.Walk
filepath.Walk 是旧 API,会为每个文件启动 goroutine,小文件多时容易卡死;filepath.WalkDir(Go 1.16+)按需遍历,可控性更强,且能跳过权限不足目录。
典型误用是把文件过滤写在 Walk 回调里,导致所有文件(包括二进制、图片)都打开尝试读取——应先用后缀或 MIME 类型预筛:
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil // 忽略权限错误
}
if !d.IsDir() && strings.HasSuffix(d.Name(), ".log") {
searchInFile(path, keyword)
}
return nil
})
- 避免用
os.Stat多次检查文件类型,fs.DirEntry的IsDir()和Type()更高效 - 搜索多个关键词时,别在每文件里重复编译正则;把
regexp.Compile提到外层,传入已编译的*regexp.Regexp - 并发搜索多个文件?用
sync.WaitGroup+goroutine,但限制 goroutine 数量(如sem := make(chan struct{}, 4)),否则 I/O 打满
忽略大小写、全词匹配等细节靠 strings.EqualFold 和正则边界
用户常以为 strings.Contains 加 strings.ToLower 就能忽略大小写,但 Unicode 场景下会出错(比如土耳其语的 I 和 i)。正确做法是用 strings.EqualFold 做等价比较,或正则启用 (?i) 标志。
“全词匹配”(如搜 err 不匹配 error)不能靠空格判断,得用正则单词边界 \berr\b:
re := regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(keyword) + `\b`) re.MatchString(line) // 安全匹配,自动转义 keyword 中的正则元字符
-
regexp.QuoteMeta必须加——否则用户输a.b会被当正则匹配任意字符,而非字面量 - 中文等无空格语言的“全词匹配”无标准解法,此时建议放弃 \b,改用前后字符是否为 Unicode 字母/数字来判断
- 性能敏感场景下,避免混合使用
strings和regexp:一次匹配就选一种,别先Contains再扔给正则二次过滤
真正难的不是写通,而是让搜索行为和用户直觉一致:换行符兼容、编码鲁棒、大文件不崩、多关键词不慢、错误路径不中断。这些细节藏在 Scanner 缓冲、WalkDir 控制流、正则预编译和 Unicode 边界处理里,漏掉任一环,线上查日志时就会卡住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











