编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

    • Shell & Bash 从0到1实战专栏
    • Shell 入门与变量
    • 流程控制与函数
    • 数据与 IO 处理
    • grep 搜索实战
      • 4.1 grep 基础搜索
        • 4.1.1 基本用法——三种调式
        • 4.1.2 正则基础 . ^ $ * + ?
        • 4.1.3 常用选项速查
      • 4.2 grep 高级技巧
        • 4.2.1 扩展正则 -E
        • 4.2.2 单词边界 \b 与词锚定
        • 4.2.3 后向引用与捕获组 \1 \2
        • 4.2.4 上下文 -A / -B / -C
        • 4.2.5 -o 仅匹配与 -c 统计——精准提取
        • 4.2.6 多模式 -e 与模式文件 -f
        • 4.2.7 二进制文件与彩色输出
      • 4.3 grep 实战案例
        • 4.3.1 日志分析——从百万行中找到问题
        • 4.3.2 代码搜索——在项目中快速定位
        • 4.3.3 数据提取——从文本中提取结构化信息
        • 4.3.4 多文件批量处理
      • 4.4 综合案例:错误日志监控告警脚本
      • 4.5 新手陷阱 Top 5
      • 4.6 综合思考题
    • sed 与 awk 编程
    • 文件查找与统计
    • 日志监控与告警
    • 备份进程与磁盘
    • 用户与服务管理
    • 网络调度与部署
    • 调试与脚本规范
    • 安全与兼容处理
    • 性能与打包分发
  • 工具脚本

  • 自动化脚本

  • ScriptHub
  • Shell-Bash
杨充
2017-05-12
目录

grep 搜索实战

# 第 4 章 grep 搜索实战

# 目录介绍

  • 4.1 grep 基础搜索
    • 4.1.1 基本用法——三种调式
    • 4.1.2 正则基础 . ^ $ * + ?
    • 4.1.3 常用选项速查
  • 4.2 grep 高级技巧
    • 4.2.1 扩展正则 -E
    • 4.2.2 单词边界 \b 与词锚定
    • 4.2.3 后向引用与捕获组 \1 \2
    • 4.2.4 上下文 -A / -B / -C
    • 4.2.5 -o 仅匹配与 -c 统计——精准提取
    • 4.2.6 多模式 -e 与模式文件 -f
    • 4.2.7 二进制文件与彩色输出
  • 4.3 grep 实战案例
    • 4.3.1 日志分析——从百万行中找到问题
    • 4.3.2 代码搜索——在项目中快速定位
    • 4.3.3 数据提取——从文本中提取结构化信息
    • 4.3.4 多文件批量处理
  • 4.4 综合案例:错误日志监控告警脚本
  • 4.5 新手陷阱 Top 5
  • 4.6 综合思考题

# 4.1 grep 基础搜索

# 4.1.1 基本用法——三种调式

grep = Global Regular Expression Print——从文本中搜索匹配正则表达式的行并打印。它是 Shell 文本处理中最常用的命令:

# ===== 调式 1:最基本的搜索 =====
grep "ERROR" app.log                        # 搜索含有 ERROR 的行

# ===== 调式 2:从管道接收输入 =====
cat app.log | grep "ERROR"                  # 效果同上(但多了无意义的 cat)
ps aux | grep nginx                         # 查找 nginx 进程
dmesg | grep "usb"                          # 从内核日志中搜索 USB

# ===== 调式 3:从多个文件搜索 =====
grep "ERROR" app.log app.log.1 app.log.2    # 多个文件——会显示文件名
grep "ERROR" /var/log/*.log                 # 通配符——搜索所有 .log 文件

# ===== 实战:三种使用方式最佳实践 =====
# ✅ 场景 1:搜索单个文件
grep -n "timeout" /etc/nginx/nginx.conf

# ✅ 场景 2:从管道接收——grep 作为过滤器
tail -f /var/log/app.log | grep "ERROR"     # 实时监控并过滤错误

# ✅ 场景 3:搜索整个目录
grep -rn "TODO" /home/user/project/ --include="*.py"  # 在 Python 源码中搜索 TODO

🔑 grep 的三种变体——本质是同一个命令的快捷方式:

命令 等价于 正则类型 记忆
grep grep -G 基础正则(BRE) g/re/p 的缩写
egrep grep -E 扩展正则(ERE) Extended
fgrep grep -F 固定字符串(无正则) Fixed string
# egrep = grep -E(扩展正则——最常用)
grep -E "[0-9]{3}-[0-9]{4}" file.txt     # 匹配电话号码

# fgrep = grep -F(固定字符串——最快)
grep -F "a.txt" file.txt                  # 把 .* 等当作文本字面量搜索
# 当你要搜索的字符串包含大量正则元字符时——用 -F 避免转义地狱

# 4.1.2 正则基础 . ^ $ * + ?

正则表达式是 grep 的灵魂——掌握元字符就能精准定位任何文本:

#!/bin/bash

# ===== . —— 匹配任意单个字符(除换行符)=====
echo -e "cat\ncut\ndog" | grep "c.t"      # 匹配:cat cut(不匹配 dog)
# . 匹配 t、u——所以 c.t 匹配 "cat" 和 "cut"

# ===== ^ —— 行首锚定 =====
grep "^ERROR" app.log                      # 匹配以 ERROR 开头的行
grep "^[[:space:]]*#" config.conf          # 匹配被空格缩进的注释行

# ===== $ —— 行尾锚定 =====
grep "error$" app.log                      # 匹配以 error 结尾的行
grep "^$" app.log                          # 匹配空行(行首后紧跟行尾)

# ===== * —— 前一个字符重复 0 次或多次 =====
grep "ab*c" file.txt                       # 匹配:ac, abc, abbc, abbbc...
# 实际含义:a + b(0次或多次) + c

# ===== + —— 前一个字符重复 1 次或多次(需要 -E)=====
# grep 基础模式不支持 +——必须加 -E
grep -E "ab+c" file.txt                    # 匹配:abc, abbc, abbbc...(最少 1 个 b)

# ===== ? —— 前一个字符重复 0 次或 1 次(需要 -E)=====
grep -E "colou?r" file.txt                 # 匹配:color, colour

# ===== [ ] —— 字符集 =====
grep "gr[ae]y" file.txt                    # 匹配:gray, grey
grep "[0-9]" file.txt                      # 包含数字的行
grep "[a-zA-Z]" file.txt                   # 包含英文字母的行
grep "[^0-9]" file.txt                     # 包含非数字字符的行(^ 在 [] 开头表示取反)

# ===== 预定义字符类 =====
grep "[[:space:]]" file.txt                # 空白字符(空格/tab等)
grep "[[:digit:]]" file.txt                # 数字 [0-9]
grep "[[:alpha:]]" file.txt                # 字母 [a-zA-Z]
grep "[[:alnum:]]" file.txt                # 字母+数字
grep "[[:punct:]]" file.txt                # 标点符号

🔑 正则元字符速查表:

元字符 含义 示例
. 任意单个字符 h.t → hat, hit, hot
^ 行首 ^# → 注释行
$ 行尾 $ → 行尾锚定
* 0 次或多次 ab*c → ac, abc, abbc
+ 1 次或多次(-E) ab+c → abc, abbc
? 0 次或 1 次(-E) colou?r → color, colour
{n,m} n 到 m 次(-E) [0-9]{3,5} → 3-5 位数字
[abc] 字符集 gr[ae]y → gray, grey
[^abc] 字符集取反 [^0-9] → 非数字
( ) 分组(-E) (ab)+ → ab, abab
\| 或(-E) cat\|dog → cat 或 dog
\b 单词边界 \bword\b → 单词 word
# ===== 字符集 {n,m} 量词(需要 -E)=====
grep -E "[0-9]{3}" file.txt                # 连续 3 位数字
grep -E "[0-9]{4,}" file.txt               # 至少 4 位数字
grep -E "[a-z]{3,5}" file.txt              # 3-5 位小写字母

# ===== | 或(需要 -E)=====
grep -E "ERROR|FATAL|CRITICAL" app.log     # 搜索三种严重级别

# ===== 分组 ( )(需要 -E)=====
grep -E "(http|https)://" url.txt          # http:// 或 https://
grep -E "(ab){2,}" file.txt                # ab 重复至少 2 次:abab, ababab...

# ===== 转义元字符——用 \ 取消特殊含义 =====
grep "1\.2\.3\.4" ip.txt                   # 搜索精确字符串 "1.2.3.4"(. 被转义为字面量)

# 4.1.3 常用选项速查

#!/bin/bash

# ===== -i —— 忽略大小写 =====
grep -i "error" app.log                    # 匹配:error, Error, ERROR, eRrOr

# ===== -v —— 反向匹配(显示不匹配的行)=====
grep -v "^#" /etc/nginx/nginx.conf         # 显示非注释行
ps aux | grep -v "grep"                    # 去掉 grep 命令自身进程

# ===== -n —— 显示行号 =====
grep -n "listen" /etc/nginx/nginx.conf     # 输出:12:listen 80;

# ===== -r / -R —— 递归搜索 =====
grep -rn "TODO" /home/user/project/        # 递归搜索整个目录——显示文件名+行号+内容

# ===== -l / -L —— 只显示文件名 =====
grep -rl "class User" /home/user/project/  # 只列出包含匹配的文件名(不显示内容)
grep -rL "class User" /home/user/project/  # 只列出不包含匹配的文件名

# ===== -c —— 统计匹配行数 =====
grep -c "ERROR" app.log                    # 输出:42(匹配的行数,不是次数)

# ===== -o —— 仅输出匹配部分 =====
echo "error at 2025-06-07 14:30:00" | grep -o "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}"
# 输出:2025-06-07

# ===== -s —— 静默模式(不显示文件不存在的错误)=====
grep -s "ERROR" /nonexistent/file.log      # 不输出错误——适合在脚本中使用

🔑 常用选项速查表:

选项 含义 场景
-i 忽略大小写 搜索时不分大小写
-v 反向匹配 过滤掉不需要的行
-n 显示行号 快速定位到原文
-r 递归搜索目录 在项目中全局搜索
-l 只显示文件名 知道哪些文件涉及
-L 不匹配的文件名 找没有某模式的文件
-c 统计行数 快速计数
-o 仅显示匹配部分 提取特定信息
-s 不显示不存在错误 脚本中静默使用
-q 安静模式(不输出) 只关心退出码
-w 整词匹配 避免部分匹配
-x 整行匹配 搜索精确行内容

-q 在脚本中的用法——只关心是否匹配到:

#!/bin/bash

if grep -q "pattern" /var/log/app.log; then
    echo "找到了"
fi

# 等价于(但 -q 更快——找到第一个就停止)
grep "pattern" /var/log/app.log > /dev/null 2>&1

# 4.2 grep 高级技巧

# 4.2.1 扩展正则 -E

基础正则(BRE)和扩展正则(ERE)的核心区别——哪些元字符需要转义:

# ===== BRE vs ERE 对比 =====
# BRE 中 + ? { } ( ) | 都被当作字面量——需要 \ 转义才获得特殊含义
# ERE 中这些字符默认就是特殊含义

# BRE(grep 默认)——元字符要加反斜杠
grep "\+" file.txt                         # 搜索 + 符号本身
grep "\?" file.txt                         # 搜索 ? 符号本身

# 那在 BRE 中怎么用 + 或 ? 的含义?
grep "ab\+c" file.txt                      # 匹配 abc, abbc...(\+ 是"1次或多次")
grep "colou\?r" file.txt                   # 匹配 color, colour

# ERE(grep -E)——元字符不需要反斜杠
grep -E "ab+c" file.txt                    # 匹配 abc, abbc...
grep -E "colou?r" file.txt                 # 匹配 color, colour

# ===== 综合对比 =====
模式                      BRE (grep)              ERE (grep -E)
数字 3-5 位               grep "[0-9]\{3,5\}"      grep -E "[0-9]{3,5}"
IP 地址                  grep "[0-9]\{1,3\}\.[0-9]\{1,3\}"   grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
cat 或 dog               grep "cat\|dog"          grep -E "cat|dog"

📌 新写 grep 命令一律用 -E——ERE 的可读性远高于 BRE,没有理由再写反斜杠地狱。

# 4.2.2 单词边界 \b 与词锚定

\b = word boundary——匹配单词的开头或结尾,避免部分匹配:

#!/bin/bash

# ===== 没有 \b——部分匹配的问题 =====
echo -e "cat\ncatalog\ncategory\ncat\ncat" | grep "cat"
# 输出:cat, catalog, category, cat, cat(包含 cat 的行全部匹配)

# ===== 有 \b——严格的单词匹配 =====
echo -e "cat\ncatalog\ncategory\ncat" | grep "\bcat\b"
# 输出:cat, cat(只匹配完整单词 "cat")

# ===== 行首/行尾锚定 vs 单词边界 =====
grep "^cat" file.txt                         # 行首的 cat——只能匹配行首
grep "cat$" file.txt                         # 行尾的 cat——只能匹配行尾
grep "\bcat\b" file.txt                      # 任何位置的完整单词 cat——最灵活

# ===== 兼容写法——没有 \b 时的替代 =====
# grep 的 \< 和 \> 等价于 \b(GNU grep 支持)
grep "\<cat\>" file.txt                      # 等价于 grep "\bcat\b"

🔑 单词边界实用模式:

# 搜索完整单词 "log"(不会匹配到 blog, catalog, dialog)
grep -w "log" file.txt                       # -w = 整词匹配(等价于 \blog\b)

# 搜索以 error 开头的单词
grep -E "\berror" app.log                    # matches: error, errorlog(不匹配 aerror)

# 搜索以 ing 结尾的单词
grep -E "ing\b" app.log                      # matches: running, walking(不匹配 ingredient)

# 搜索 3 位数字单词(独立数字)
grep -E "\b[0-9]{3}\b" file.txt              # 匹配 123, 456(不匹配 1234 或 0123)

# 4.2.3 后向引用与捕获组 \1 \2

捕获组 () 不仅能分组——还能"记住"匹配到的内容,在后面引用:

#!/bin/bash

# ===== 后向引用基础——查找重复单词 =====
echo "the the quick brown fox" | grep -E "(\b\w+\b) \1"
# 输出:the the(\1 引用第一个捕获组匹配到的内容)

# ===== 查找相邻重复单词(写作中的常见错误)=====
echo -e "hello world\nhello hello world\nthis this is a test" | grep -E "(\b\w+\b) \1"
# 输出:
# hello hello world
# this this is a test

# ===== 匹配成对标签 =====
echo "<h1>Title</h1><p>Text</p>" | grep -E "<([a-z]+)>.*</\1>"
# \1 确保关闭标签和打开标签同名
# 匹配 <h1>Title</h1> 和 <p>Text</p>

# ===== 匹配重复的 IP 地址段 =====
echo "192.192.168.1" | grep -E "([0-9]{1,3})\.\1"  # 找到前两段相同的 IP

🔑 后向引用实际应用:

# ===== 查找 HTML 中的重复属性 =====
grep -E 'class="([^"]+)"[^>]*class="\1"' index.html   # 同一个 class 出现两次

# ===== 格式化日志中的重复时间戳 =====
# 假设日志格式:2025-06-07 2025-06-07 ERROR something
grep -E "([0-9]{4}-[0-9]{2}-[0-9]{2}) \1" app.log    # 两次相同日期

# ===== 捕获组 + -o 提取特定字段 =====
echo "name=张三 age=25 city=深圳" | grep -oE 'name=(\w+)' | grep -oE '\w+$'
# 输出:张三(先匹配 name=张三,再提取最后的单词)

# 4.2.4 上下文 -A / -B / -C

搜索匹配行时,通常也需要看它的前后文——grep 提供了三组选项:

#!/bin/bash

# ===== -A N —— After(匹配行后 N 行)=====
grep -A 3 "FATAL" app.log                  # 显示匹配行及之后 3 行

# ===== -B N —— Before(匹配行前 N 行)=====
grep -B 2 "Exception" app.log              # 显示匹配行及之前 2 行

# ===== -C N —— Context(匹配行前后各 N 行)=====
grep -C 5 "CRITICAL" app.log               # 显示匹配行及前后各 5 行——最常用

# ===== 分组上下文——用 --group-separator =====
grep -C 2 "ERROR" app.log --group-separator="──────────"
# 不同匹配组之间用 ────────── 分隔

🔑 实战:分析堆栈跟踪:

# Java/Python 报错时通常会有一段堆栈——-A 可以顺便捕获它
grep -A 15 "Traceback (most recent call last)" app.log
# 显示 Python 异常及其 15 行堆栈

# 显示 SQL 错误及前后上下文
grep -B 2 -A 20 "SQLSTATE" mysql.log
# 看到 SQL 语句及其错误详情

# 4.2.5 -o 仅匹配与 -c 统计——精准提取

#!/bin/bash

# ===== -o —— 仅输出匹配部分 =====
# 默认 grep 输出一整行——-o 只输出匹配到的部分
echo "error at 2025-06-07 14:30:00" | grep -oE "[0-9]{2}:[0-9]{2}:[0-9]{2}"
# 输出:14:30:00(只提取时间)

# ===== -o 配合 -E 提取 IP 地址 =====
echo "访问来自 192.168.1.1,状态 200" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"
# 输出:192.168.1.1

# ===== -o 提取所有邮箱 =====
grep -oE "\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b" contacts.txt

# ===== -c —— 统计匹配行数 =====
grep -c "ERROR" app.log                    # 输出包含 ERROR 的行数

# ===== 注意:-c 是行数,不是匹配次数 =====
# 如果一行里有 3 个 ERROR——-c 只计 1
# 要统计总次数:用 -o + wc -l
grep -o "ERROR" app.log | wc -l            # 统计所有 ERROR 出现的总次数

# ===== -o + 排序:提取并统计 =====
grep -oE "\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b" access.log \
    | sort \
    | uniq -c \
    | sort -rn \
    | head -10
# 提取所有 IP,排序后统计每个 IP 出现的次数,取 Top 10

# 4.2.6 多模式 -e 与模式文件 -f

#!/bin/bash

# ===== -e —— 多个模式任意匹配(或关系)=====
grep -e "ERROR" -e "FATAL" -e "CRITICAL" app.log
# 搜索三个关键词中的任意一个——等价于 grep -E "ERROR|FATAL|CRITICAL"

# ===== -e 处理以 - 开头的模式 =====
grep -e "--max-age" nginx.conf              # 搜索以 - 开头的字符串
# 如果不加 -e:grep "--max-age" 会被解析为选项而不是模式!

# ===== -f —— 从文件读取模式 =====
# patterns.txt 内容:
# ERROR
# FATAL
# CRITICAL
# Timeout:
grep -f patterns.txt app.log                # 同时搜索文件中列出的所有模式

# ===== 模式文件的优势 =====
# ① 模式可以包含很多——不限制数量
# ② 方便管理和复用
# ③ 可以配合其他工具生成模式

# 实战:从告警配置生成模式
grep "^[^#]" alert_patterns.cfg | grep -v "^$" | grep -f - app.log
# 读取 config 中非注释非空的行为模式——-f - 从 stdin 读取模式

# 4.2.7 二进制文件与彩色输出

#!/bin/bash

# ===== 彩色输出(默认已是——否则手动加 --color)=====
grep --color=auto "ERROR" app.log           # auto:管道时不输出颜色
grep --color=always "ERROR" app.log         # always:即使管道也输出颜色
grep --color=never "ERROR" app.log          # never:禁止颜色
# 在脚本中推荐 --color=never——颜色代码会污染管道输出

# ===== 处理二进制文件 =====
grep -a "text" binary_file                  # -a:把二进制当文本处理
grep -I "pattern" *.log                     # -I:忽略二进制文件

# ===== 处理大文件——性能优化 =====
# --line-buffered:逐行输出(适合 tail -f 实时监控)
tail -f app.log | grep --line-buffered "ERROR"

# --max-count:找到 N 个匹配后停止
grep --max-count=5 "ERROR" app.log          # 只找前 5 个

# 4.3 grep 实战案例

# 4.3.1 日志分析——从百万行中找到问题

#!/bin/bash

# ===== 场景 1:统计不同严重级别的错误数量 =====
echo "=== 错误统计 ==="
for level in INFO WARN ERROR FATAL; do
    count=$(grep -c "$level" /var/log/app.log)
    printf "  %-10s %s\n" "$level" "$count"
done

# ===== 场景 2:查找时间范围内的日志 =====
# 假设日志格式:[2025-06-07 14:30:00] [ERROR] ...
grep "2025-06-07 1[4-5]" /var/log/app.log    # 14:xx 到 15:xx 的日志

# ===== 场景 3:分析 5xx 错误 =====
grep -E '" [5][0-9][0-9] ' access.log        # HTTP 5xx 状态码
grep -c -E '" 5[0-9][0-9] ' access.log       # 5xx 总数
grep -E '" 5[0-9][0-9] ' access.log | wc -l  # 同上(-c 更快)

# ===== 场景 4:慢查询分析(超过 1 秒的请求)=====
# 假设格式:/api/users  耗时 2345ms
grep -E "耗时 [1-9][0-9]{3,}ms" app.log      # 1000ms+ 的慢请求

# ===== 场景 5:查看某个用户的操作记录 =====
grep "user_id=9527" /var/log/app.log | tail -50

# ===== 场景 6:排除已知的无害错误 =====
grep -E "ERROR|FATAL" app.log | grep -v "Connection reset by peer"
# 只显示真实的错误,排除已知的安全警告

# 4.3.2 代码搜索——在项目中快速定位

#!/bin/bash

# ===== 基础代码搜索 =====
grep -rn "class User" src/                  # 定位 User 类定义
grep -rn "def get_user" src/                # 定位 Python 函数定义
grep -rn "interface.*Service" src/          # 搜索包含 interface 和 Service 的行

# ===== 指定文件类型搜索 =====
grep -rn "TODO\|FIXME\|HACK" src/ --include="*.py"   # Python 文件的待办
grep -rn "console.log" src/ --include="*.js"           # JS 遗留调试
grep -rn "\.only\(" tests/ --include="*.test.js"      # 定位跳过的测试

# ===== 排除特定目录 =====
grep -rn "api_key" . --exclude-dir=node_modules --exclude-dir=.git
grep -rn "password" . --exclude="*.min.js" --exclude="vendor/*"

# ===== 搜索 import 依赖 =====
grep -rn "^import " src/ | sort | uniq       # 汇总所有 import 语句

# ===== 搜索函数调用关系 =====
grep -rn "send_email(" src/                  # 找到所有调用 send_email 的地方

# 4.3.3 数据提取——从文本中提取结构化信息

#!/bin/bash

# ===== 提取 IP 地址 =====
grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" /var/log/secure | sort -u
# 从登录日志中提取所有唯一 IP

# ===== 提取邮箱 =====
grep -oE "\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b" contacts.txt

# ===== 提取 URL =====
grep -oE "https?://[a-zA-Z0-9./?=_-]+" markdown.md | sort -u

# ===== 提取数字 =====
grep -oE "[0-9]+" data.txt | paste -sd+ | bc   # 求和所有数字

# ===== 提取 JSON 字段 =====
echo '{"name":"张三","age":25,"city":"深圳"}' | grep -oE '"name":"[^"]*"' | cut -d'"' -f4
# 输出:张三

# ===== 提取带前后缀的上下文 =====
# 从 HTML 中提取标题
grep -oP '<title>\K[^<]+(?=</title>)' index.html
# \K 丢弃之前匹配的内容,(?=...) 向前查找——只提取标题文本

# 4.3.4 多文件批量处理

#!/bin/bash

# ===== 批量替换前的搜索确认 =====
# 在修改前确认哪些文件会受影响
grep -rl "old_hostname" /etc/ --include="*.conf" 2>/dev/null
# 输出匹配文件列表——安全确认后再执行 sed 替换

# ===== 跨文件统计 =====
grep -rh "ERROR" /var/log/ --include="*.log" | sort | uniq -c | sort -rn | head -20
# -h:不显示文件名(合并所有匹配行)

# ===== 在多个日志文件中找最新的错误 =====
# 按修改时间排序日志文件,在每个文件里搜索
ls -t /var/log/app.log* | while read -r f; do
    count=$(grep -c "ERROR" "$f" 2>/dev/null)
    [[ "$count" -gt 0 ]] && echo "$f: $count 个错误"
done

# ===== 统计不同文件的错误数 =====
for f in /var/log/*.log; do
    errors=$(grep -c "ERROR" "$f" 2>/dev/null || echo 0)
    printf "%-40s %d\n" "$f" "$errors"
done | sort -t' ' -k2 -rn | head -10

# ===== 搜索并生成 HTML 报告 =====
cat <<'HTML' > search_report.html
<html><body><h1>搜索结果</h1><pre>
HTML
grep -rn "TODO" src/ --include="*.py" >> search_report.html
echo "</pre></body></html>" >> search_report.html

# 4.4 综合案例:错误日志监控告警脚本

把本章全部知识——基础搜索 + 扩展正则 + 上下文 + 计数 + 多文件 + 管道组合——串联成一个生产级日志监控脚本:

#!/bin/bash
# log_monitor.sh —— 错误日志监控告警脚本
# 用法:./log_monitor.sh <日志目录> [选项]
# 示例:./log_monitor.sh /var/log/myapp --severity ERROR --window 60 --alert 10

set -euo pipefail

# ===== 1. 配置 =====
declare -A CONFIG
CONFIG[log_dir]="${1:-/var/log/myapp}"
CONFIG[severity]="${2:-ERROR}"
CONFIG[time_window]="${3:-300}"              # 统计时间窗口(秒),默认 5 分钟
CONFIG[alert_threshold]="${4:-10}"           # 超过多少条触发告警
CONFIG[pattern_file]=""                      # 自定义模式文件

# 预定义严重级别模式
declare -A LEVEL_PATTERNS
LEVEL_PATTERNS[INFO]="^[0-9]{4}-[0-9]{2}-[0-9]{2}.*\[INFO\]"
LEVEL_PATTERNS[WARN]="^[0-9]{4}-[0-9]{2}-[0-9]{2}.*\[WARN\]"
LEVEL_PATTERNS[ERROR]="^[0-9]{4}-[0-9]{2}-[0-9]{2}.*\[ERROR\]"
LEVEL_PATTERNS[FATAL]="^[0-9]{4}-[0-9]{2}-[0-9]{2}.*\[FATAL\]"

# ===== 2. 函数定义 =====

# 彩色输出
RED='\033[0;31m'; GREEN='\033[0;32m'
YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'

log_info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }

usage() {
    cat <<EOF
用法:$0 <日志目录> [选项]

选项:
  --severity LEVEL   搜索级别(INFO/WARN/ERROR/FATAL,默认 ERROR)
  --window SEC       时间窗口(秒,默认 300)
  --alert N          N 条匹配时触发告警(默认 10)
  --pattern "REGEX"  自定义正则模式
  --show            显示详细错误行(默认只显示统计)
  -h, --help        显示帮助
EOF
    exit 0
}

# 时间窗口内的日志检查
check_logs_in_window() {
    local log_file="$1"
    local pattern="$2"
    local window="$3"

    # 获取最近 window 秒内的日志条数
    # 假设日志时间戳格式:[2025-06-07 14:30:00]
    if [[ -f "$log_file" ]]; then
        local now timestamp cut_time count
        now=$(date '+%s')
        timestamp=$(date -d "@$((now - window))" '+%Y-%m-%d %H:%M:%S')

        # 跳过时间戳比较——用 tail 代替(更简单)
        # 用 wc -l 估算日志量,然后用 tail 查看末尾部分
        local total_lines
        total_lines=$(wc -l < "$log_file" 2>/dev/null || echo 0)
        local tail_lines=500
        if (( total_lines > tail_lines )); then
            count=$(tail -n "$tail_lines" "$log_file" | grep -cE "$pattern" 2>/dev/null || echo 0)
        else
            count=$(grep -cE "$pattern" "$log_file" 2>/dev/null || echo 0)
        fi

        echo "$count"
        return 0
    fi
    echo 0
}

# 显示详细的错误行(带上下文)
show_error_details() {
    local log_file="$1"
    local pattern="$2"
    local context_lines="${3:-2}"

    if [[ -f "$log_file" ]]; then
        echo ""
        echo "━━━ 详细错误($log_file)━━━━━━━━━━━━━"
        grep -n -C "$context_lines" -E "$pattern" "$log_file" \
            --color=never | head -100
        echo ""
    fi
}

# 发送告警(这里用 echo 模拟——实际可以替换为邮件/钉钉/飞书)
send_alert() {
    local subject="$1"
    local body="$2"

    echo "[ALERT] $subject"
    echo "$body"

    # 实际使用时可替换为:
    # echo "$body" | mail -s "$subject" admin@company.com
    # curl -X POST -H "Content-Type: application/json" \
    #   -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$subject\n$body\"}}" \
    #   https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY
}

# ===== 3. 主逻辑 =====

main() {
    local log_dir="${CONFIG[log_dir]}"
    local severity="${CONFIG[severity]}"
    local window="${CONFIG[time_window]}"
    local threshold="${CONFIG[alert_threshold]}"

    # 检查目录
    if [[ ! -d "$log_dir" ]]; then
        log_error "日志目录不存在:$log_dir"
        exit 1
    fi

    log_info "开始监控:$log_dir"
    log_info "级别:$severity | 窗口:${window}s | 阈值:$threshold"

    # 获取搜索正则模式
    local pattern
    if [[ -n "${CONFIG[pattern_file]}" ]] && [[ -f "${CONFIG[pattern_file]}" ]]; then
        # 从文件读取模式
        pattern=$(paste -sd'|' "${CONFIG[pattern_file]}")
    elif [[ -n "${LEVEL_PATTERNS[$severity]}" ]]; then
        pattern="${LEVEL_PATTERNS[$severity]}"
    else
        pattern="$severity"
    fi

    # 查找日志文件
    local log_files
    log_files=$(find "$log_dir" -maxdepth 1 -name "*.log" -type f | sort -r | head -5)
    if [[ -z "$log_files" ]]; then
        log_error "没有找到 .log 文件"
        exit 1
    fi

    # 清点最新活动
    local total_errors=0
    local total_files=0
    local alert_files=()

    while IFS= read -r lf; do
        if [[ ! -f "$lf" ]]; then
            continue
        fi

        local count
        count=$(check_logs_in_window "$lf" "$pattern" "$window")
        ((total_files++))
        ((total_errors += count))

        local fname
        fname=$(basename "$lf")
        local fsize
        fsize=$(du -h "$lf" | cut -f1)
        printf "  %-30s %8s  %s\n" "$fname" "$fsize" "[$count 条匹配]"

        if (( count > threshold )); then
            alert_files+=("$fname($count)")
        fi

        # 显示详细错误
        if (( count > 0 )); then
            show_error_details "$lf" "$pattern"
        fi
    done <<< "$log_files"

    # 输出汇总
    echo ""
    echo "━━━ 汇总 ━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "  检查文件数:$total_files"
    echo "  匹配总数:$total_errors"
    echo "  时间窗口:最近 ${window} 秒"

    # 判断是否触发告警
    if (( total_errors > threshold )); then
        log_warn "错误数 $total_errors 超过阈值 $threshold——触发告警!"

        local alert_msg
        alert_msg=$(printf "  超限文件:%s\n" "$(IFS=,; echo "${alert_files[*]}")")
        send_alert "[监控告警] $severity 级别错误 $total_errors 次($HOSTNAME)" \
            "时间:$(date '+%Y-%m-%d %H:%M:%S')\n目录:$log_dir\n错误数:$total_errors\n$alert_msg"
    else
        log_info "状态正常($total_errors / $threshold)"
    fi

    # 退出码
    if (( total_errors > threshold )); then
        return 2    # 告警退出码
    elif (( total_errors > 0 )); then
        return 1    # 有错误但未超阈值
    else
        return 0    # 一切正常
    fi
}

main "$@"

案例知识融合:这个脚本覆盖了本章全部核心技术——基础搜索(grep -c 计数、grep -n 行号)、扩展正则 -E([0-9]{4}-[0-9]{2}-[0-9]{2} 日期匹配、LEVEL_PATTERNS 预定义正则)、上下文 -C(show_error_details 显示前后行)、-o 与 -c(尾行快速计数)、多文件处理(find + while read 遍历日志文件)、管道组合(du -h + cut 获取文件大小)、同时使用了上一章的 关联数组 和 Here Document。

# 4.5 新手陷阱 Top 5

# 陷阱 说明
1 -E 遗漏——元字符被当文本 grep "(error|fatal)" 搜索的是字面量 (error|fatal)——加 -E 才表示或
2 正则 . 误匹配一切 grep "1.2.3.4" 匹配了 1a2b3c4——. 是任意字符!IP 搜索要转义 \.
3 * 含义误解 grep "error*" 匹配 erro + 任意个 r——不是"任意字符"!.* 才是"任意"
4 -c 统计少算 grep -c "ERROR" 统计的是匹配行数——一行 3 个 ERROR 只计 1。计数用 -o \| wc -l
5 大文件无限制搜索 grep "pattern" huge_file.sql 会消耗大量内存——用 --max-count 或 head 限制

陷阱 1 详解——-E 的缺失:

# ❌ 没加 -E——() 和 | 被当作字面量
grep "(ERROR|FATAL)" app.log
# 实际搜索的是文本 "(ERROR|FATAL)"——不是 ERROR 或 FATAL!

# ✅ 加 -E——() 和 | 获得正则含义
grep -E "ERROR|FATAL" app.log              # 搜索 ERROR 或 FATAL
grep -E "(ERROR|FATAL)" app.log            # 同上

# ✅ 或者用 \-E 和多个 -e
grep -e "ERROR" -e "FATAL" app.log         # 等价

陷阱 3 详解——* 和 .* 的区别:

# ❌ 错误:
# grep "error*"——让 * 作用于 r
# 含义:erro + 0 或多个 r
# 匹配:erro, error, errorr, errorrr...

# ✅ 正确:匹配 "error" 后再跟任意内容
grep "error.*" app.log                     # .* 才是"任意数量任意字符"
grep "error" app.log                       # 匹配 error——最简单

# 常见误区总结:
# 错误 "error*"         → 匹配 erro, error, errorr(字面理解反直觉)
# 正确 "error.*"        → 匹配 error, error123, error:timeout
# 正确 "error"          → 只匹配 error(精确)
# 正确 "err[aeiou]r"    → 匹配 error, error...不,匹配 errar, errec, errid...

陷阱 5 详解——大文件优化:

#!/bin/bash

# ❌ 大文件无限制——会卡住
grep "SELECT" huge_dump.sql                # 500MB 文件——等吧

# ✅ 优化方案 1:先定位范围
grep -n "SELECT" huge_dump.sql | head -20  # 只看前 20 个
grep --max-count=20 "SELECT" huge_dump.sql # 找到 20 个就停

# ✅ 优化方案 2:用 LC_ALL 加速
LC_ALL=C grep "SELECT" huge_dump.sql       # C locale——不用处理多字节,快 2-5 倍

# ✅ 优化方案 3:分块并行(大文件专用)
split -n l/4 huge_dump.sql chunk_          # 分成 4 块
for f in chunk_*; do grep "SELECT" "$f" > "$f.out" & done
wait
cat chunk_*.out > results.txt && rm chunk_*

# ✅ 优化方案 4:用更快的工具
# 对于 1GB+ 的文件,考虑用 ripgrep(rg)替代 grep
# rg "pattern" huge_file.sql               # 比 grep 快 5-10 倍

# 4.6 综合思考题

  1. grep 为什么不直接支持 + 和 ?? ——Ken Thompson 在 1974 年实现 grep 时,只实现了 ed 编辑器中的基础正则(* 和 [])。+ 和 ? 是后来才加入扩展正则的。当时 Ken 是怎么选择正则特性的?如果你今天重新设计 grep,会让 -E 成为默认模式吗?

  2. grep -F(fgrep)的性能优势 ——固定字符串搜索不需要编译正则状态机,grep -F 使用 Boyer-Moore 算法。在搜索纯文本 "localhost" 时,grep -F 比 grep 快多少(可以自己用 time 命令测试)?为什么 -F 在某些场景下甚至比 grep "localhost" 快 2-3 倍?

  3. --binary-files=text vs -a vs strings ——在二进制文件中搜索文本,grep -a 可能匹配到不可打印的控制字符导致误报。什么时候应该先用 strings binary_file | grep pattern 再搜索?这个处理流程的缺陷是什么?

  4. grep 和其他工具的记忆取舍 ——grep 的正则、-A/-B/-C、-o、-l 等选项用了就忘不了。相反,sed 的保持空间、awk 的 NR/NF 等选项长期不用就很难记住。这种"记忆差距"对 Shell 脚本的可维护性意味着什么?"一个用复杂正则链替代 Python 脚本"的 grep 一行命令,是否真的比一个简单可读的 Python 脚本更好?

  5. ripgrep(rg)对 grep 的取代 ——grep 诞生于 1974 年,rg 诞生于 2016 年。rg 默认递归、默认忽略 .gitignore、默认彩色输出、在大型代码库上比 grep 快 5-10 倍。在什么情况下你仍然应该选择 grep 而不是 rg?(提示:不可安装 rg 的生产环境、POSIX 兼容性要求、Docker 最小镜像)

#Shell#文本
上次更新: 2026/06/28, 17:55:19
数据与 IO 处理
sed 与 awk 编程

← 数据与 IO 处理 sed 与 awk 编程→

最近更新
01
科学方法实践论法
06-28
02
辩证思维矛盾论法
06-28
03
毛选中的调查观念
06-28
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式