1. 引言为什么需要三剑客在 Linux 运维、开发、安全审计中日志是系统的“黑匣子”。无论是 Nginx 记录的每一次 HTTP 请求还是系统内核输出的每一行消息都可能隐藏着性能瓶颈、安全威胁或程序错误。面对动辄几 GB 甚至 TB 级的日志文件图形化工具往往力不从心而专业的日志管理系统如 ELK又显得笨重。此时经典的三剑客grep、awk、sed成为每名工程师必须掌握的利器grep– 快速过滤出感兴趣的行如错误、特定 IP、时间范围。awk– 对结构化文本进行切片、统计、计算生成报表。sed– 非交互式编辑文本替换、删除、转换格式。三者均基于正则表达式遵循 Unix 哲学“每个程序只做一件事并把它做好”通过管道组合可以实现任意复杂的日志分析流水线。本文特点从零基础命令开始循序渐进到复杂实战案例每段命令都附带详细解释和示例输出。全文约 2 万字涵盖大量可直接复用的脚本片段帮助你彻底掌握日志分析技能。2. 第一剑grep – 文本搜索的艺术2.1 基础语法与常用选项grep的名字来自ed编辑器命令g/re/p全局正则表达式打印。基本用法bashgrep [选项] 模式 [文件...]常用选项选项长格式作用-i--ignore-case忽略大小写-v--invert-match反向匹配输出不包含模式的行-c--count只输出匹配的行数-n--line-number显示行号-l--files-with-matches只输出包含匹配的文件名-L--files-without-match只输出不包含匹配的文件名-r--recursive递归搜索目录-o--only-matching只输出匹配到的部分而非整行-E--extended-regexp使用扩展正则表达式ERE-P--perl-regexp使用 Perl 兼容正则PCRE-A n--after-contextn显示匹配行及其后 n 行-B n--before-contextn显示匹配行及其前 n 行-C n--contextn显示匹配行前后各 n 行示例bash# 在 /var/log/syslog 中查找所有包含 error 的行忽略大小写 grep -i error /var/log/syslog # 统计包含 Failed password 的行数 grep -c Failed password /var/log/auth.log # 显示匹配行及其后 2 行 grep -A 2 segfault /var/log/kern.log2.2 正则表达式快速入门grep 默认使用基本正则表达式BRE元字符需要转义如\(\)、\{。推荐使用-E开启扩展正则表达式更直观。常用元字符元字符含义示例.任意单个字符a.c匹配abc,aXc*前一个字符重复 0 次以上ab*匹配a,ab,abb前一个字符重复 1 次以上ab匹配ab,abb?前一个字符 0 或 1 次ab?匹配a,ab[ ]字符集合[0-9]任意数字[^ ]反向字符集合[^a-z]非小写字母^行首^Error以 Error 开头$行尾done$以 done 结尾|或error|fail( )分组(abc)重复 abc 多次{n,m}重复次数区间[0-9]{2,4}2-4 位数字示例bash# 匹配 IPv4 地址简单版 grep -Eo ([0-9]{1,3}\.){3}[0-9]{1,3} access.log # 匹配以 2024 年开头的日期行 grep ^2024 /var/log/syslog # 匹配以 ERROR 或 WARNING 开头的行 grep -E ^(ERROR|WARNING) app.log2.3 实战grep 过滤日志文件场景1从 Nginx 访问日志中找出 404 错误bashgrep 404 /var/log/nginx/access.log注意常见 log_format 中状态码位于双引号之后例如... GET /missing HTTP/1.1 404 ...因此用 404 可精确定位。场景2提取特定时间段的日志假设日志行格式为2024-05-20 10:15:32 ...提取 10:00 到 10:59 之间的行bashgrep 2024-05-20 10: /var/log/syslog更精确的时间范围10:15:00 到 10:15:59bashgrep 2024-05-20 10:15: /var/log/syslog场景3排除包含 DEBUG 的行只看警告及以上bashgrep -v DEBUG /var/log/application.log | grep -E INFO|WARN|ERROR|FATAL场景4查找包含 IP 地址 192.168.1.100 的所有行并显示行号bashgrep -n 192\.168\.1\.100 /var/log/secure注意点号需要转义或用-F固定字符串模式grep -F 192.168.1.1002.4 上下文控制与递归搜索显示匹配行的前后文bash# 显示匹配行及前后各 5 行 grep -C 5 Out of memory /var/log/messages # 只显示后 10 行常用于查看错误后的日志 grep -A 10 panic /var/log/kern.log递归搜索目录bash# 搜索 /etc/nginx 下所有 .conf 文件中包含 proxy_pass 的行 grep -r --include*.conf proxy_pass /etc/nginx/ # 排除 .log 文件 grep -r --exclude*.log error /var/log/只输出文件名-l与统计匹配数-cbash# 找出哪些日志文件包含 ssh grep -l ssh /var/log/*.log # 每个文件匹配到的行数 grep -c ssh /var/log/*.log3. 第二剑awk – 数据提取与报告生成器awk是一种模式扫描与处理语言尤其适合处理表格形式的文本如日志中的空格/定界符分隔字段。它的名字来自三位作者 Aho、Weinberger、Kernighan。3.1 awk 的工作模型与基本结构工作流程自动读取输入文件的每一行。对每一行依次执行用户提供的pattern { action }规则。如果没有指定 pattern则默认匹配所有行如果没有指定 action则默认{ print }打印整行。基本语法bashawk pattern { action } input-file最简单的例子bash# 打印每行等同于 cat awk { print } /var/log/syslog # 打印包含 error 的行 awk /error/ { print } /var/log/syslog3.2 字段与分隔符awk 自动将每一行分割成字段默认使用空白字符空格或制表符作为分隔符。字段引用$1第一个字段$2第二个字段...$0整行NF当前行的字段数内置变量可直接使用更改分隔符使用-F选项或内置变量FSField Separator。bash# 以冒号为分隔符打印 /etc/passwd 的第一列用户名和第三列UID awk -F: { print $1, $3 } /etc/passwd # 等价写法 awk BEGIN{ FS: } { print $1, $3 } /etc/passwd对于日志文件Nginx 访问日志通常使用空格分隔但某些字段如请求体包含空格所以标准格式下仍可用$直接提取。但更复杂的日志如 Apache 组合格式可能需要使用双引号作为分隔符的辅助处理。3.3 内置变量与运算符常用内置变量变量含义NR当前已读取的行数行号NF当前行的字段数FS字段分隔符默认空白OFS输出字段分隔符默认空格RS记录分隔符默认换行符ORS输出记录分隔符默认换行符FILENAME当前输入文件名运算符算术、比较、逻辑与 C 语言类似bash# 打印偶数行 awk NR % 2 0 file # 如果第3字段大于 100打印整行 awk $3 100 file # 逻辑与字段1等于root 且 字段3大于等于1000 awk $1 root $3 1000 /etc/passwd3.4 模式匹配与条件判断正则表达式匹配bash# 匹配包含 404 的行 awk /404/ access.log # 匹配第9字段状态码为 404 的行更精确 awk $9 404 access.log混合条件bash# 状态码是 404 或 500并且请求时长 5 秒假设第10字段是响应时间 awk ($9 404 || $9 500) $10 5 access.logBEGIN 与 END 块BEGIN在处理任何输入行之前执行常用于初始化变量、打印表头。END在所有行处理完毕后执行常用于输出汇总结果。bashawk BEGIN { print 开始分析... } { count } END { print 总行数:, count } file3.5 数组与循环awk 支持关联数组下标可以是字符串或数字无需声明。统计IP出现次数bash# access.log 中第一个字段是客户端IP常见格式 awk { ip[$1] } END { for (i in ip) print i, ip[i] } access.log找出出现次数最多的前10个IPbashawk { ip[$1] } END { for (i in ip) print ip[i], i } access.log | sort -rn | head -10多维数组模拟bash# 统计每个状态码按小时的发生次数假设第4字段是时间 [10/May/2024:13:30:00 awk { split($4, arr, :); hour arr[2]; status $9; cnt[hour][status] } END { for (h in cnt) { for (s in cnt[h]) print h, s, cnt[h][s] } } access.log3.6 格式化输出与内置函数printf 格式化类似 C 语言的printfbashawk { printf %-15s %5d\n, $1, $3 } file常用内置函数函数作用示例length(str)返回字符串长度length($0)整行长度substr(s, m, n)子串substr($4, 2, 3)split(s, a, sep)分割成数组split($4, a, :)tolower(str)转小写tolower($1)toupper(str)转大写sprintf(fmt, expr)格式化返回字符串时间处理自定义函数awk 本身没有内置日期解析但可以通过字符串函数提取。3.7 实战awk 统计分析日志案例1计算 Nginx 访问日志中所有请求的平均响应时间假设日志格式自定义为text192.168.1.1 - - [10/May/2024:13:30:15 0800] GET /api/data HTTP/1.1 200 1024 0.045最后字段是响应时间秒。bashawk { sum $NF; count } END { if (count0) print 平均响应时间:, sum/count, 秒 } access.log案例2统计每小时请求总数与平均大小假设日期时间在$4例如[10/May/2024:13:30:15先提取小时。bashawk { split($4, dt, :); hour dt[2]; req_count[hour]; total_bytes[hour] $10; # 假设第10字段是 body_bytes_sent } END { for (h in req_count) printf %02d:00 请求数%d 平均字节%.2f\n, h, req_count[h], total_bytes[h]/req_count[h] } access.log | sort案例3筛选出响应时间大于1秒的慢请求并输出IP、URL、时间bashawk $NF 1 { print $1, $7, $NF } access.log案例4统计 5xx 错误占比bashawk { total; if ($9 500 $9 600) errors_5xx } END { printf 总请求: %d, 5xx错误: %d, 占比: %.2f%%\n, total, errors_5xx, (errors_5xx/total)*100 } access.log4. 第三剑sed – 流式编辑的瑞士军刀sedStream Editor是一个非交互式文本编辑器常用于批量替换、删除、插入文本行特别适合在管道中处理日志的清洗和转换。4.1 sed 工作流程与基本命令sed 逐行读取输入将当前行存入模式空间pattern space然后执行编辑命令最后输出模式空间的内容除非使用-n抑制。基本语法bashsed [选项] 命令 输入文件常用选项-n不自动打印模式空间只打印p命令指定的行。-i直接修改原文件谨慎使用建议先备份。-e允许多个命令。-f从脚本文件读取命令。-r或-E使用扩展正则表达式。常用命令命令作用示例s/old/new/替换默认只替换每行第一个s/error/ERROR/d删除整行/^$/d删除空行p打印当前行-n 10p打印第10行a\在当前行后追加文本/pattern/a\新行i\在当前行前插入文本/pattern/i\新行c\替换整行/pattern/c\新内容y字符转换类似 try/abc/ABC/q退出 sed10q打印前10行后退出4.2 替换命令的深度解析替换命令格式[地址]s/正则表达式/替换字符串/标志地址可以是一个行号、一个正则表达式或范围。例如bash# 只替换第10行 sed 10s/foo/bar/ file # 替换从第5行到第10行 sed 5,10s/foo/bar/ file # 替换匹配 error 的行 sed /error/s/foo/bar/ file分隔符除了/可以使用任何字符如#、|当路径中包含/时非常有用bashsed s#/usr/local/bin#/opt/bin# file替换标志标志含义g全局替换行内所有匹配都替换i忽略大小写GNU sedp打印替换成功的行常与-n配合w将结果写入文件数字只替换每行第N次匹配示例bash# 将每行第一个 dog 替换为 cat sed s/dog/cat/ file # 全局替换 sed s/dog/cat/g file # 只替换每行第二个 dog sed s/dog/cat/2 file # 忽略大小写替换 sed s/error/WARNING/gi file使用分组与反向引用在正则表达式中用\( \)分组替换时用\1、\2引用。bash# 交换第一和第二个字段以空格分隔 sed s/\([^ ]*\) \([^ ]*\)/\2 \1/ file # 将日志中的日期格式从 10/May/2024 转为 2024-05-10 sed s#\([0-9]\{2\}\)/\([A-Z][a-z]\{2\}\)/\([0-9]\{4\}\)#\3-\2-\1# file月份转换需要进一步映射这里只是示例。4.3 删除、插入、追加与修改删除行 (d)bash# 删除空行 sed /^$/d file # 删除包含 debug 的行 sed /debug/d file # 删除第5行 sed 5d file # 删除第10行到最后一行 sed 10,$d file插入与追加 (i\ 和 a)注意i\在匹配行前插入a\在后追加。多行文本需要用反斜杠换行。bash# 在匹配到 ERROR 的行前面插入一行 ALERT sed /ERROR/i\ ALERT log.txt # 在每行后追加空行双倍行距 sed a\\ file # 注意两个反斜杠第一个转义换行符 # 在第3行后插入一行 sed 3a\New line file修改整行 (c)bash# 将以 listen 开头的行替换为 listen 8080; sed /^listen/c\listen 8080; nginx.conf4.4 地址范围的多种指定方式地址范围可以混合行号和正则bash# 从包含 BEGIN 的行到包含 END 的行之间执行操作 sed /BEGIN/,/END/ s/foo/bar/g file # 从第10行到包含 stop 的行 sed 10,/stop/ d file # 从第5行到文件末尾 sed 5,$ s/old/new/ file4.5 实战sed 清理与转换日志案例1移除 ANSI 颜色转义码常见于彩色终端输出bashsed s/\x1b\[[0-9;]*m//g colored.log案例2提取 Nginx 日志中的请求方法、URL 和状态码假设日志格式1.2.3.4 - - [10/May/2024:13:30:15] GET /index.html HTTP/1.1 200 512bashsed -n s/.*\([A-Z]*\) \([^ ]*\) .* \([0-9]\{3\}\).*/\1 \2 \3/p access.log解释-n抑制自动打印p标志打印替换后的行。正则捕获方法、URL和状态码。案例3匿名化 IP 地址保留前两段bashsed -r s/([0-9]{1,3}\.){2}[0-9]{1,3}(\.[0-9]{1,3})?/xxx.xxx./ access.log # 或者替换为固定值 sed -r s/[0-9]{1,3}(\.[0-9]{1,3}){3}/xxx.xxx.xxx.xxx/g access.log案例4在每行开头添加行号类似 cat -nbashsed file | sed N;s/\n/\t/sed 输出行号然后通过N将下一行原内容追加到模式空间再替换换行符为制表符。案例5删除日志中所有注释和空行用于清理配置bashsed -e s/#.*// -e /^[[:space:]]*$/d config.conf5. 三剑合璧组合使用的高效流水线5.1 管道与重定向的艺术三剑客各自发挥特长通过管道|串联形成强大的日志分析链。典型模式grep 快速过滤行→ 2.sed 清洗格式→ 3.awk 结构化统计5.2 典型组合模式模式1grep awkbash# 找出所有 5xx 错误然后统计每个 IP 的错误次数 grep 5[0-9][0-9] access.log | awk { ip[$1] } END { for (i in ip) print i, ip[i] } | sort -k2 -rn模式2awk sed sortbash# 提取响应时间超过1秒的请求将日期格式转换为 YYYY-MM-DD awk $NF 1 { print $4, $7, $NF } access.log | sed s/\[\([0-9]\{2\}\)\/\([A-Za-z]\{3\}\)\/\([0-9]\{4\}\)/\3-\2-\1/ | sort模式3grep sed awk 多阶段处理复杂任务分解bash# 步骤1过滤出包含 Failed password 的行 # 步骤2用 sed 提取用户名和IP # 步骤3用 awk 统计每个 IP 的失败次数 grep Failed password /var/log/auth.log | \ sed -n s/.*Failed password for .* from \([0-9.]*\) .*/\1/p | \ awk { count[$0] } END { for (ip in count) print count[ip], ip } | \ sort -rn | head -20模式4循环处理多个文件bashfor log in /var/log/nginx/access.log-*; do echo $log awk { total; if ($9500) err } END { print Total:, total, 5xx:, err, Rate:, err/total*100 % } $log done6. 实战一Nginx 访问日志深度分析6.1 Nginx 日志格式简介Nginx 默认访问日志格式combinedtextlog_format combined $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent;示例行text192.168.1.100 - - [10/May/2024:13:30:15 0800] GET /api/users HTTP/1.1 200 1024 https://example.com/ Mozilla/5.0 ...字段位置$1: 192.168.1.100$2: -$3: -$4: [10/May/2024:13:30:15 0800] 注意有方括号$5: GET /api/users HTTP/1.1$6: 200$7: 1024$8: https://example.com/$9: Mozilla/5.0 ...如果自定义了 log_format 包含响应时间$request_time可增加字段。6.2 统计独立 IP 访问量bashawk { ip[$1] } END { for (i in ip) print i, ip[i] } access.log | sort -k2 -rn | head -10输出示例text203.0.113.5 15420 198.51.100.2 8923 ...6.3 按状态码分组计数bashawk { status[$6] } END { for (s in status) print s, status[s] } access.log | sort -n或者计算百分比bashawk { total; status[$6] } END { for (s in status) printf %3s %8d %6.2f%%\n, s, status[s], (status[s]/total)*100 } access.log6.4 统计访问量最高的 URLURL 位于第5个字段双引号内需要提取其中的请求路径。例如GET /api/users HTTP/1.1。我们可以用awk的match或split。bashawk { match($5, /^[A-Z] ([^ ]) HTTP/, arr); url arr[1]; if (url ! ) count[url] } END { for (u in count) print count[u], u } access.log | sort -rn | head -206.5 分析响应时间与慢请求假设我们在 log_format 中添加了$request_time则日志行最后会多一个字段例如$10为请求时间单位秒。那么bash# 平均响应时间 awk { sum$10; n } END { print Average:, sum/n s } access.log # 找出最慢的10个请求 awk { print $10, $1, $5 } access.log | sort -rn | head -10 # 响应时间超过3秒的请求按URL分组统计次数 awk $10 3 { match($5, /^[A-Z] ([^ ]) HTTP/, arr); url arr[1]; slow[url] } END { for (u in slow) print slow[u], u } access.log | sort -rn | head -106.6 实时监控与报警触发结合tail -f和awk可以实现实时监控bashtail -f access.log | awk $6 500 { print ALERT: 5xx error from, $1, Status, $6 } $10 5 { print SLOW: Request from, $1, took, $10, seconds } 将报警输出到系统日志或发送邮件需额外脚本。7. 实战二Nginx 错误日志排错Nginx 错误日志通常位于/var/log/nginx/error.log格式如text2024/05/10 13:30:15 [error] 12345#0: *1234 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.1, server: example.com, request: GET /api HTTP/1.1, upstream: http://127.0.0.1:8080, host: example.com7.1 常见错误类型提取bash# 统计每种错误级别的数量error, warn, crit, alert 等 grep -oP \[\K(emerg|alert|crit|error|warn|notice|info|debug)(?\]) /var/log/nginx/error.log | sort | uniq -c使用 awk 更结构化bashawk match($0, /\[([a-z])\]/, m) { level[m[1]] } END { for (l in level) print l, level[l] } error.log7.2 按时间段统计错误频率错误日志第一字段是日期可提取小时bashawk { hour substr($2,1,2); count[hour] } END { for (h in count) print h, count[h] } error.log | sort7.3 关联 upstream 与后端问题提取连接拒绝错误对应的 upstreambashgrep Connection refused error.log | grep -oP upstream: \K[^] | sort | uniq -c找出最常出现问题的后端服务器。8. 实战三系统日志安全审计与故障排查系统日志因发行版不同位置有差异RHEL/CentOS 使用/var/log/messagesDebian/Ubuntu 使用/var/log/syslog认证日志为/var/log/auth.log或/var/log/secure。8.1 /var/log/messages 与 /var/log/syslog格式示例syslog 标准textMay 10 13:30:15 hostname kernel: [12345.678] Out of memory: Kill process8.2 统计登录失败与暴力破解尝试统计每个IP的失败次数sshdbashgrep Failed password /var/log/auth.log | \ awk { match($0, /from ([0-9.])/, ip); if (ip[1]!) count[ip[1]] } END { for (i in count) print count[i], i } | \ sort -rn | head -10统计无效用户登录尝试bashgrep Invalid user /var/log/auth.log | awk { print $(NF-2) } | sort | uniq -c | sort -rn查看最近10次成功的登录bashgrep Accepted password /var/log/auth.log | tail -108.3 查找 OOM内存溢出事件bashgrep -i out of memory /var/log/messages | awk { print $1, $2, $3, $NF }也可以找出被 kill 的进程bashgrep Kill process /var/log/messages | awk -F name { print $2 } | cut -d , -f1 | sort | uniq -c8.4 追踪服务启动/停止/崩溃例如追踪 systemd 服务nginx的状态变化bashjournalctl -u nginx --since today | grep -E Started|Stopped|Failed若使用传统 syslog可查看bashgrep nginx /var/log/messages | grep -E starting|exiting|failed使用 awk 按时间段筛选bashawk /May 10 13:/ /nginx/ /error/ /var/log/messages9. 高级技巧与性能优化9.1 处理超大日志文件G级别使用LC_ALLC设置 locale 为 C可以大幅提升 grep/awk 速度避免 UTF-8 多字节处理开销。bashLC_ALLC grep pattern huge.log使用LANGC类似效果。利用--mmap已废弃现代 grep 默认优化无需手动。分而治之将大文件按时间分割成小文件后再分析。避免在 awk 中使用过多的数组如果数组过大导致内存不足考虑使用sort | uniq -c代替。9.2 并行处理加速使用xargs -P或 GNUparallel并行处理多个文件bash# 并行统计多个日志文件的行数 ls *.log | xargs -P 4 -I{} wc -l {} # 使用 parallel 并行执行 awk parallel awk {sum\$NF} END {print FILENAME, sum/NR} {} ::: *.log9.3 正则表达式优化原则避免贪婪匹配使用[^]*替代.*当知道引号内的内容。尽量使用固定字符串grep -F比正则快得多。锚定行首行尾^和$可加速匹配。使用grep过滤先行再交给awk减少 awk 处理的输入量。示例先 grep 过滤出包含 error 的行再 awk 统计。bashgrep -i error huge.log | awk { ... }9.4 与时间戳结合的区间筛选日志时间格式通常不是简单的数字awk 无法直接比较。解决方案使用date命令转换时间戳为 epoch但效率低。使用sed或awk提取日期时间字符串然后借助外部工具如awk调用系统命令但极慢。更实际的方法使用grep配合正则提取时间范围例如提取某一天的所有日志bash# 提取 2024-05-10 的日志假设行首为 2024-05-10 grep ^2024-05-10 syslog # 提取 10:00 到 10:59 awk /^2024-05-10 10:/, /^2024-05-10 11:/ syslog注意awk的范围模式/start/,/stop/会匹配从第一个匹配 start 的行到第一个匹配 stop 的行之后不再匹配适用于提取单一连续块。对于非连续的时间区间可以写复杂的条件bashawk { # 假设时间字段位置为 $3格式 10:15:32 split($3, t, :); hour t[1]; minute t[2]; if ((hour 10 minute 15) || (hour 11 minute 30)) print } syslog10. 总结与学习资源三剑客在日志分析领域之所以长盛不衰是因为它们简单快速一行命令完成复杂统计。可组合性管道让各工具发挥极致。无处不在任何 Linux/Unix 环境都自带无需额外安装。通过本文的入门讲解和大量实战案例你已经掌握了使用grep快速过滤日志行利用正则表达式精确匹配。使用awk对日志字段进行切片、统计、汇总生成报表。使用sed批量编辑日志格式清洗脏数据。结合三者解决 Nginx 访问日志分析、错误排查、系统安全审计等真实问题。进阶学习资源书籍《sed awk 第二版》Dale Dougherty Arnold Robbins书籍《精通正则表达式》Jeffrey FriedlGNU 官方文档info grep、info awk、info sed在线测试regex101.com选择 POSIX 或 GNU 风格