1. grep 是什么
grep(Global Regular Expression Print)是 Unix 下最核心的文本搜索工具.它的工作很简单:读取文本,逐行扫描,把包含指定模式的行打印出来.
grep 就像你拿一支荧光笔在读一本书--你说"把出现这个词的句子全给我标出来",grep 就一页页翻,找到一行画一行,只返回命中的行,其余全部忽略.
它通常出现在两个位置:
- 直接在文件上搜索
- 作为管道链条中的过滤器--上一级命令的输出经过 grep 筛掉不关心的行,只把相关数据传给下一级
grep 的家族
grep(使用 BRE 基本正则,? + { | ( ) 需要转义才能被识别为元字符) ·
egrep(等价于 grep -E,使用 ERE 扩展正则,特殊字符不用转义,更直观) ·
fgrep(等价于 grep -F,按字面文本匹配,不解释正则,速度快).
日常建议直接用 grep -E,正则语法更接近其他语言,少写一堆反斜杠.
2. 基本用法
2.1 语法与第一条 grep
|
来一个真实的例子:
|
两个值得注意的点:
- 默认大小写敏感--
Apple没被选中 - 默认部分匹配--
pineapple命中了,因为它包含apple这个子串
2.2 --color:高亮匹配部分
在输出中把匹配到的文本用颜色标出来,一目了然:
|
现代 Linux 发行版通常已经在 ~/.bashrc 中把 grep 别名成了 grep --color=auto(输出到终端时自动上色,输出到管道时不渲染颜色代码).
3. 常用选项
3.1 选项速查
| 选项 | 含义 | 示例 |
|---|---|---|
-i |
忽略大小写(case-insensitive) | grep -i "error" log.txt |
-v |
反转匹配--选不包含模式的行(invert) | grep -v "#" config.conf |
-n |
显示行号(line number) | grep -n "TODO" *.py |
-c |
统计匹配行count | grep -c "ERROR" app.log |
-l |
只列出list 匹配的文件名 | grep -l "FIXME" src/**/*.py |
-w |
整word 匹配 | grep -w "is" script.sh |
-o |
only 只输出匹配的那部分文本 | grep -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log |
-r / -R |
recursive 递归搜索目录 | grep -r "import" src/ |
-E |
启用Extended 扩展正则 | `grep -E "error |
3.2 重点选项详解
-i:忽略大小写
|
-n:显示行号
|
行号让后续定位变得便捷--配合 vim / VS Code :行号 跳转,是最常用的组合之一.
-c:只统计数量
|
4. 正则表达式
grep 名称中的 RE 就是 Regular Expression(正则表达式).理解了正则,grep 才真正从"搜索关键词"升级为"模式匹配引擎".
4.1 BRE vs ERE:grep 的两种正则方言
grep 默认使用 BRE(Basic Regular Expression),grep -E(或 egrep)使用 ERE(Extended Regular Expression).核心区别在于:BRE 中 ? + { } | ( ) 需要加反斜杠才能当元字符用,ERE 不需要.
|
建议日常统一用 grep -E--ERE 语法和 Python/JavaScript 的正则更接近,省得记两套转义规则.本节后续所有示例都使用 grep -E.
4.2 正则速查表
| 模式 | 含义 | 例子 |
|---|---|---|
. |
任意一个字符(除换行) | h.t 匹配 hot,hat,h3t |
* |
前一个字符出现 0 次或多次 | ab*c 匹配 ac,abc,abbc |
+ |
前一个字符出现 1 次或多次 | ab+c 匹配 abc,abbc,不匹配 ac |
? |
前一个字符出现 0 次或 1 次 | colou?r 匹配 color,colour |
^ |
行首 | ^# 匹配以 # 开头的行(注释) |
$ |
行尾 | ;$ 匹配以分号结尾的行 |
[abc] |
字符集--a,b 或 c 中的任意一个 | [Hh]ello 匹配 Hello,hello |
[^abc] |
否定字符集--不是 a,b,c 的任意字符 | [^0-9] 匹配非数字 |
[a-z] |
字符范围 | [A-Z][a-z]+ 匹配首字母大写的单词 |
{n} |
恰好 n 次 | [0-9]{4} 匹配 4 位数字 |
{n,} |
至少 n 次 | [0-9]{3,} 匹配 3 位以上数字 |
{n,m} |
n 到 m 次 | [a-z]{3,8} 匹配 3–8 个字母 |
| ` | ` | 或(alternation) |
() |
分组 + 捕获 | `(error |
\b |
单词边界 | \blog\b 只匹配 log,不匹配 blog |
grep 正则 vs 编程语言正则
grep 的正则是面向行的--^ 和 $ 表示行首和行尾,不是整个文本的开头和结尾.没有 \d(用 [0-9] 代替),没有 \w(用 [a-zA-Z0-9_] 代替),也没有非贪婪匹配(*?).这些是它与 Python/JS 正则在习惯上的最大差异.
4.3 实战模式
|
5. 上下文控制:-A -B -C
搜索日志时,光看到匹配行往往不够--需要知道前后发生了什么.grep 提供了三个选项来控制上下文行数:
| 选项 | 含义 | 记忆 |
|---|---|---|
-A n |
显示匹配行及其后 n 行 | After |
-B n |
显示匹配行及其前 n 行 | Before |
-C n |
显示匹配行及其前后各 n 行 | Context |
|
注意不同匹配块之间的 -- 分隔符.这是 grep 帮你加的,防止上下文混在一起.
6. 递归搜索:-r 与 -R
-r(或 -R)让 grep 递归进入目录,搜索其中的所有文件.两者的区别仅在于符号链接的处理:-R 会跟随符号链接,-r 不会--绝大多数场景用 -r 即可.
|
6.1 --include / --exclude:过滤搜索范围
递归搜索通常会匹配到不需要的文件(二进制文件,node_modules,.git 目录等).--include 和 --exclude 支持 glob 通配符来限定范围:
|
--exclude-dir 排除的是目录名而不是路径.比如 --exclude-dir=build 会排除所有名为 build 的目录(无论嵌套多深).
7. 管道中的 grep
grep 的最大价值不在独立使用,而是作为管道链条中的筛选关卡.回顾第 10 篇--管道把上一个命令的 stdout 传给下一个命令的 stdin,grep 恰好是 stdin 的天然消费者.
7.1 过滤模式:筛出感兴趣的行
|
7.2 统计模式:数一数有多少
|
7.3 多重 grep 串联:逐步收紧条件
多个 grep 串联等于AND 逻辑--每一级筛掉一批行,最后剩下同时满足所有条件的:
|
8. 进阶技巧
8.1 反向匹配:-v
-v 是运维的日常--"把不关心的行排除掉"比"把关心的行挑出来"更常用:
|
为什么 ps | grep ... 总要多 grep -v grep?
ps aux | grep "nginx" 的结果里总会包含 grep 进程自己:grep nginx 这行也含 "nginx".加一个 | grep -v "grep" 把它过滤掉,或者用正则技巧:ps aux | grep "[n]ginx"--[n] 匹配 n,但 grep 进程的命令行是 [n]ginx(方括号),这个模式不匹配自己,于是巧妙地避开了自引用.
8.2 精确词匹配:-w
-w 让 grep 只匹配完整的单词(被非单词字符包围):
|
-w 等价于在模式两边各加一个 \b(单词边界),不需要手动写了.
8.3 只列文件名:-l
递归出几百个匹配时,往往只需要知道"哪些文件里有":
|
8.4 只输出匹配部分:-o
配合正则,-o 能从行中抠出真正想要的数据:
|
快速回顾
- grep = 逐行扫描 + 模式匹配:读 stdin 或文件,按行检查,把包含模式的行输出
- 核心选项:
-i忽略大小写 ·-v反转 ·-n行号 ·-c计数 ·-r递归 ·-E扩展正则 - ERE 更友好:
grep -E的正则语法比默认 BRE 更接近 Python/JS--+|()不需要反斜杠 - 上下文控制:
-A(After) /-B(Before) /-C(Context) 控制上下文行数,看日志排错时必用 - 递归搜索:
grep -r "pattern" dir/,配合--include/--exclude限定文件范围 - 管道过滤:
cmd | grep "条件" | cmd2--多级串联等于 AND 逻辑 - 精准操作:
-w精确匹配单词 ·-l只列文件名 ·-o只抠匹配部分 - 排除自引用:grep 常出现在 ps 结果中,用
grep -v grep或grep "[p]attern"排除自己
动手练习
- 大小写匹配:创建一个测试文件,混入大小写不同的同一单词,分别用
grep和grep -i搜索,体会区别 - ERE 或运算:用
grep -E "error|warning|fatal"搜索某个日志文件(或/var/log/system.log),比较与三次普通 grep 的效率差异 - 递归搜索:找一个项目目录,用
grep -rn "TODO" .找出所有待办注释,尝试加上--include限定文件类型 - 上下文查看:用
grep -B 2 -A 3 "ERROR"查看错误日志的上下文,观察--分隔符的行为 - 排除自引用:运行
ps aux | grep "[t]erminal"看看是否避免了 grep 自引用,再试试去掉方括号观察差异 - 提取匹配:用
grep -o从一段文本中提取所有符合某种模式的数据(如grep -Eo "[0-9]+" file提取所有数字) - 统计行数:用
grep -c统计一个项目目录下所有源文件的代码行数中,有多少行是 import 语句