Linux: 文本搜索——grep 与正则表达式
在成千上万个文件中找到你要的那一行——这是 grep 的看家本领。它可能是 Linux 上用得最频繁的命令。
📋 前置知识:需要先掌握以下内容
- 第13课:管道与过滤器
1. 你将学到
- grep 基本搜索和常用选项
- grep -r 递归搜索代码
- 基础正则表达式
- 扩展正则表达式
- 日志分析实战
2. 一个在百个文件中找代码的故事
(1) 痛点:想改一个接口名,不知道哪些文件用了它
小明需要将所有 Python 文件中调用 requests.get 的地方改为 httpx.get。项目有 100 多个文件,他不可能一个个打开看。
(2) grep 递归搜索
grep -rn "requests\.get" --include="*.py" src/
输出:
src/api/client.py:15: response = requests.get(url, headers=headers)
src/api/users.py:42: data = requests.get(f"{API_URL}/users")
src/api/orders.py:78: result = requests.get(ORDER_URL)
3 个文件中有调用——不到 1 秒就找到了。
(3) 收益:从大海捞针到指哪打哪
小明养成了习惯:要改代码前先用 grep -rn 找到所有相关位置,确认后再动手。
3. 知识点讲解
(1) grep 基本用法
# 基本搜索
grep "pattern" filename
# 常用选项组合
grep -rn "TODO" src/ # 递归搜索,显示行号
grep -i "error" log.txt # 忽略大小写
grep -v "debug" log.txt # 反向匹配(不包含 debug 的行)
grep -c "failed" log.txt # 只计数
grep -l "main" *.py # 只显示匹配的文件名
grep -A5 -B5 "exception" log.txt # 显示匹配行的前后各 5 行
| 选项 | 含义 |
|---|---|
-i |
忽略大小写 |
-v |
反向选择(显示不匹配的行) |
-c |
只输出匹配行数 |
-n |
显示行号 |
-r 或 -R |
递归搜索子目录 |
-l |
只显示包含匹配的文件名 |
-L |
只显示不包含匹配的文件名 |
-w |
匹配整个单词 |
-A NUM |
显示匹配行后 NUM 行 |
-B NUM |
显示匹配行前 NUM 行 |
-C NUM |
显示匹配行前后各 NUM 行 |
(2) 基础正则表达式
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
匹配任意单个字符 | h.t 匹配 hit / hot / hat |
^ |
行首 | ^root 匹配以 root 开头的行 |
$ |
行尾 | bash$ 匹配以 bash 结尾的行 |
* |
前一个字符重复 0 次或多次 | ab*c 匹配 ac / abc / abbc |
[abc] |
匹配集合中任意一个 | [Hh]ello 匹配 Hello 或 hello |
[^abc] |
匹配不在集合中的字符 | [^0-9] 匹配非数字字符 |
\< |
单词开头 | \<root 匹配行首的 root |
\> |
单词结尾 | root\> 匹配行尾的 root |
. 匹配任意单个字符——如果你需要匹配字面意义的点号(如 IP 地址中的 .),需要用反斜杠转义:grep "192\.168\." file 而非 grep "192.168." file(后者会匹配 192X168Y 等错误结果)。
(3) 扩展正则表达式(grep -E)
基础正则中 +、?、|、{}、() 需要转义才能用。扩展正则直接使用这些元字符(用 grep -E 或 egrep):
ℹ️ 说明:基础正则和扩展正则的主要区别在于,扩展正则(
grep -E)中的+、?、|、{}、()不需要反斜杠转义即可使用。在基础正则中,grep "error\|warning"需要转义|,而grep -E "error|warning"直接写。建议优先使用grep -E,语法更清晰。
| 元字符 | 含义 | 示例 |
|---|---|---|
+ |
前一个字符重复 1 次或多次 | ab+c 匹配 abc / abbc 但不匹配 ac |
? |
前一个字符出现 0 次或 1 次 | colou?r 匹配 color 或 colour |
| ` | ` | 或 |
{n,m} |
重复 n 到 m 次 | [0-9]{3,5} 匹配 3-5 位数字 |
() |
分组 | `(error |
▶ 示例:基本搜索
# 在文件中搜索
grep "root" /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# 不区分大小写
echo -e "Error\nerror\nERROR" | grep -i "error"
# 三行全匹配
# 计数
grep -c "nologin" /etc/passwd
# 输出:16
# 显示行号
grep -n "www-data" /etc/passwd
# 33:www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
输出:
TEXT 📖 仅展示root:x:0:0:root:/root:/bin/bash Error error ERROR 16 33:www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
▶ 示例:反向匹配
# 排除注释行
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
# 排除 debug 日志
grep -v "DEBUG" app.log | grep "ERROR"
# 不包含特定扩展名的文件
ls -la | grep -v "\.md$"
输出:
TEXT 📖 仅展示Port 22 PermitRootLogin no PasswordAuthentication no 2026-07-07 10:23:45 ERROR Database connection failed 2026-07-07 10:24:12 ERROR Timeout waiting for response drwxr-xr-x 2 alice alice 4096 Jul 7 10:00 src -rw-r--r-- 1 alice alice 1234 Jul 7 10:00 config.yaml
▶ 示例:递归搜索代码
# 搜索项目中所有 TODO 注释
grep -rn "TODO\|FIXME\|HACK" --include="*.py" src/
# 在特定类型文件中搜索
grep -rn "def " --include="*.py" --include="*.js" .
# 排除 node_modules 搜索
grep -rn "api_key" --exclude-dir=node_modules --exclude-dir=.git .
# 只显示文件名
grep -rl "class User" src/
输出:
TEXT 📖 仅展示src/api/client.py:23:# TODO: Add retry logic src/models/user.py:45:# FIXME: Handle edge case src/utils/helpers.py:12:# HACK: Temporary workaround src/api/client.py:10:def get_user(user_id): src/models/user.py:5:def validate_email(email): config/settings.py:3:api_key = "sk-xxx" src/models/user.py src/auth.py
grep -rn 是代码搜索的黄金组合——-r 递归搜索目录,-n 显示行号,配合 --include 限制文件类型。在大型项目中,grep -rn "pattern" --include="*.py" src/ 可以在一秒内定位目标代码。
▶ 示例:使用上下文
# 显示匹配行前后各 3 行
grep -C 3 "NullPointerException" app.log
# 显示匹配行后 5 行
grep -A 5 "Traceback" python_error.log
# 显示匹配行前 2 行
grep -B 2 "500" access.log
输出:
TEXT 📖 仅展示10:23:01 INFO Processing request 10:23:02 WARN Memory usage high 10:23:03 ERROR NullPointerException at UserService.java:45 10:23:04 INFO Retrying operation 10:23:05 WARN Connection pool low Traceback (most recent call last): File "app.py", line 42, in handle_request result = process(data) File "processor.py", line 15, in process return transform(input) ValueError: Invalid input data 192.168.1.100 - - [07/Jul/2026:10:23:44] "POST /api/data" 192.168.1.100 - - [07/Jul/2026:10:23:45] "POST /api/data HTTP/1.1" 500 0
▶ 示例:正则表达式实战
# 匹配邮箱地址
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt
# 匹配 IP 地址
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
# 匹配 URL
grep -E "https?://[^ ]+" index.html
# 匹配日期(YYYY-MM-DD)
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" logs/*.log
# 匹配以数字开头和字母结尾的行
grep "^[0-9].*[a-zA-Z]$" file.txt
输出:
TEXT 📖 仅展示alice@example.com bob.smith@company.org contact@my-site.net 192.168.1.100 - - [07/Jul/2026] ... 10.0.0.45 - - [07/Jul/2026] ... https://example.com/api/users http://internal.server.local/status 2026-07-07 2026-07-08 1abc 23x
▶ 综合示例:日志错误分析
#!/bin/bash
# analyze-errors.sh - 日志错误分析
LOG_FILE="${1:-/var/log/syslog}"
echo "=== 错误分析报告 ==="
echo "源文件: $LOG_FILE"
echo ""
echo "1. 各类错误数量:"
grep -i "error" "$LOG_FILE" | awk '{print $5}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "2. 最近 5 条错误:"
grep -i "error" "$LOG_FILE" | tail -5
echo ""
echo "3. 关键告警(OOM / Disk Full / Connection Refused):"
grep -iE "oom|out of memory|disk full|no space left|connection refused" "$LOG_FILE"
echo ""
echo "4. 错误趋势(按小时统计)":
grep -i "error" "$LOG_FILE" | grep -oP "\d{2}:\d{2}" | cut -d: -f1 | sort | uniq -c | sort -rn
❓ 常见问题
Q: grep 和正则有什么区别? A: grep 是一个命令行工具,用于在文件中搜索匹配模式的行;正则表达式(regex)是一种描述文本模式的语法。grep 支持正则表达式作为搜索模式,但正则本身只是语法规则,还可以用在 sed、awk、Python 等许多工具和语言中。
Q:
grep -r和grep -R一样吗? A: 基本一样。-R会追踪符号链接,-r默认不追踪。日常使用选哪个都可以。
Q: 怎么忽略大小写搜索? A: 用
-i选项:grep -ir "error" /var/log/。
Q: 为什么 grep 有时找不到中文字符? A: 可能是 locale 问题。中文字符在 UTF-8 编码下是多字节的,
grep默认按字节匹配。试试export LANG=zh_CN.UTF-8或grep -P(Perl 正则模式)。
Q:
grep -v有什么用? A: 反向选择——输出不匹配模式的行。配合其他 grep 使用可以"排除"某些行,例如grep -v "^#"排除注释行,grep -v "^$"排除空行。
📖 小节
grep "pattern" file搜索文件,-r递归目录,-n显示行号- 基础正则:
.任意字符、^行首、$行尾、*重复、[abc]集合 - 扩展正则(
grep -E):+一次以上、?零或一次、|或、()分组 -v反向、-i忽略大小写、-c计数、-l只显示文件名-A/-B/-C显示上下文行
📝 作业
- 基础题(难度⭐):在
/etc/passwd中搜索使用/bin/bash作为登录 Shell 的用户,然后在/var/log/syslog(或一个日志文件)中搜索错误并用-c统计数量 - 进阶题(难度⭐⭐):递归搜索一个项目目录,找出所有 FIXME 和 TODO 注释,然后用
grep -v排除 .git 目录后统计项目代码行数 - 挑战题(难度⭐⭐⭐):用正则表达式匹配日志文件中的所有 IP 地址,并结合
sort和uniq -c统计每个 IP 的出现次数,找出访问最多的前 10 个 IP