首页 / 资讯中心 / 文章详情

Linux文本处理三剑客:grep、sed与awk实战解析

Linux文本处理三剑客:grep、sed与awk实战解析 ★ FEATURED ARTICLE
Linux 文本处理工具这三个词组合在一起可能第一反应是“这有什么好讲的不就是 grep、sed、awk 嘛”。但如果你真在服务器上排查过 800MB 的日志文件被满屏的报错刷到眼花或者需要从一段接口返回里精确抠出某个字段你会发现这些看似“古早”的工具在关键时刻比任何重型监控系统都管用。这篇文章就围绕我在 Linux 下最常用的文本处理思路展开重点拆解 grep、sed、awk 三件套的核心用法以及它们怎样组合起来解决真实场景里的日志分析、配置修改、数据统计问题。无论你是刚接触 Linux 的运维新人还是写了两年脚本但总在正则上踩坑的后端工程师这里的实操细节都能直接拿来用。1. 先搞清楚文本处理到底在解决什么1.1 文本处理不是“文本编辑”很多刚入行的朋友会把文本处理和 vim、nano 之类的编辑器混为一谈认为“处理文本就是打开文件改一改”。其实在 Linux 的运维和开发场景里文本处理更多地指向批量、自动化、可复现地对文本内容进行筛选、提取、替换、统计。比如你有 10 万个请求日志要求统计每个接口的平均响应时间或者一个配置文件里有上百个旧 IP 地址需要一次性替换成新地址。这种任务如果靠人眼打开文件一行一行去看既不现实也不优雅。这里就要引入一个核心概念文本流。Linux 的哲学是一切皆文件而文件在命令行世界里通常是按行读取的。grep、sed、awk 这类工具本质上都是“按行处理文本”的流式工具它们读入一行、处理一行、输出一行不会把整个文件一次性塞进内存。这也是为什么它们能轻松应对 GB 级日志文件的原因——内存占用可控性能稳定。1.2 工具三件套的分工逻辑在 Linux 文本处理工具里最核心的三个命令是 grep、sed、awk它们是所有高级玩法的基础。grep负责“找”按正则表达式筛选出匹配的行或记录。sed负责“改”按行对文本做替换、删除、插入、打印等流式编辑操作。awk负责“算”按字段列切分文本支持条件判断、循环和内置函数能做统计汇总。举个生活化类比把文本文件想象成一条流水线上的零件grep 是质检员负责把不合格的零件挑出来扔掉只保留符合标准的sed 是修理工照着图纸把零件上错的部分替换掉、删掉或者补上新零件awk 是数据员把零件拆开测量每一个参数统计尺寸分布、计算废品率。三者各管一段组合起来几乎能覆盖所有文本处理需求。1.3 适用场景与目标读者这篇文章的内容围绕“Linux 常用命令中的文本处理工具”展开既包括最基本的命令格式也会涉及正则在 shell 中的转义细节、awk 的字段分隔原理、多工具组合实现一个完整日志分析的案例。适合以下人群参考刚接触 Linux 的新手想知道除了 ls、cd 之外还有哪些必须掌握的命令运维工程师需要快速分析 nginx 日志、系统日志或批量修改配置文件后端开发者想在本地调试时快速提取接口参数或排查异常堆栈嵌入式开发者在交叉编译环境里需要处理编译日志和配置脚本。我接下来分享的内容前提是大家已经能正常登录 Linux 机器并具备最基本的命令执行经验。如果你连cd /var/log都还不熟建议先花半小时把目录切换和 ls 查看玩明白再来读这篇。2. 动手前的准备环境与测试数据2.1 环境要求在开始写命令之前先确认环境。绝大多数 Linux 发行版默认已经安装了 grep、sed、awk不需要额外步骤。你可以用一条命令验证版本grep --version | head -1 sed --version | head -1 awk --version | head -1如果提示 command not found多半是精简安装或容器环境。可以通过系统包管理器安装Debian/Ubuntu 用apt install grep sed gawkCentOS/Rocky 用yum install grep sed gawk。我自己常用的 awk 是 gawkGNU awk它比 mawk 功能更全对 UTF-8 中文支持也更友好强烈建议在 Debian 系环境里安装 gawk。2.2 准备一份测试日志实际操作时最好有一份不太大但足够真实的日志文件。下面我直接给一段命令在/tmp下生成一个模拟 nginx 访问日志里面包含不同状态码、不同接口、不同 IP方便后续各种验证mkdir -p /tmp/text-lab cd /tmp/text-lab cat access.log EOF 192.168.1.10 - - [10/Oct/2024:13:55:36 0800] GET /api/login HTTP/1.1 200 532 curl/7.68.0 192.168.1.11 - - [10/Oct/2024:13:56:01 0800] POST /api/order HTTP/1.1 500 120 - Mozilla/5.0 192.168.1.12 - - [10/Oct/2024:13:56:22 0800] GET /static/index.css HTTP/1.1 304 0 Mozilla/5.0 192.168.1.10 - - [10/Oct/2024:13:57:09 0800] GET /api/user/info HTTP/1.1 200 891 Mozilla/5.0 192.168.1.13 - - [10/Oct/2024:13:58:44 0800] GET /api/login HTTP/1.1 404 211 Wget/1.20 192.168.1.14 - - [10/Oct/2024:14:01:33 0800] GET /api/order/list HTTP/1.1 200 1024 Mozilla/5.0 192.168.1.11 - - [10/Oct/2024:14:02:18 0800] POST /api/order HTTP/1.1 200 88 Mozilla/5.0 192.168.1.15 - - [10/Oct/2024:14:03:05 0800] GET /api/login HTTP/1.1 500 0 - curl/7.68.0 EOF这份日志虽然只有 8 行但字段结构完整IP、时间、请求方法、路径、协议、状态码、字节数、User-Agent。后续调用的所有命令都能在这个基础上复现。你完全可以自己再加 200 行甚至更多处理逻辑是一样的。2.3 先学会用文档和帮助在临时环境里试命令最忌讳“背参数”。Linux 的命令参数太多没有人能全记住。正确的学习方式是man grep man sed man awk这三个命令的手册都很长但不必一次读完。我的建议是只关注 SYNOPSIS 和 DESCRIPTION 的前两段剩下的在实际使用时通过/关键字去搜索。比如想知道 sed 怎么用正则替换在 man 页面里输入/substitute就能直接定位到 s 命令的讲解。这个方法效率比刷文档高得多。3. grep能“找”出问题才算入门3.1 grep 的核心原理与三种模式grep 这个名字来自global search regular expression and print它的核心就是按正则匹配输出匹配成功的行。使用时有三种常用模式grep pattern file # 普通模式固定字符串匹配 grep -E pattern file # 扩展正则常见于复杂匹配 grep -P pattern file # perl 兼容正则可选有人会问普通模式为什么要加-E呢因为 grep 默认使用的是基础正则BRE很多符号含义受限。比如在 BRE 里并不是“匹配一次或多次”的意思而是一个普通字符只有加了-E才会变成扩展正则可以识别的量词。所以只要正则里出现了、?、|、()这些符号直接上-E最省心。实际工作中我非常建议把-E当一个默认习惯用。比如从日志里找出所有状态码为 500 或 502 的请求grep -E (500|502) /tmp/text-lab/access.log注意我用了双引号和末尾空格来限定状态码字段避免把 5003 之类也匹配进去。这种“限定边界”的习惯很重要否则很容易把不该匹配的行捞出来。3.2 实战从 nginx 日志里提取指定状态码的 IP回到刚才的测试日志。我想知道“哪些 IP 产生了 500 错误”先筛选错误行再提取 IP。第一步很简单grep 500 /tmp/text-lab/access.log这个空格写法是为了匹配“状态码 500”而不是“字节数 500”。第二步是提取 IP我会配合-o选项把精确匹配的部分单独输出grep -E 500 /tmp/text-lab/access.log | grep -oE ^[0-9]\.[0-9]\.[0-9]\.[0-9]第一条 grep 找错误行第二条 grep 从错误行开头提取 IPv4 地址。这里-o的用处是只输出正则匹配到的部分而不是整行非常适合“从行里抠字段”的场景。执行后得到两个 IP192.168.1.11和192.168.1.15。3.3 高频选项与性能注意点除了-E、-o还有几个选项几乎每天都要用到-v反向匹配输出不匹配的行。看日志时最常用比如排除调试信息grep -v DEBUG。-c只统计匹配行数。比如统计错误数量grep -c 500 access.log。-n显示行号。排查配置错误时能直接定位到第几行。-i忽略大小写。搜索 ERROR、Error、error 时可统一匹配。-l列出包含匹配内容的文件名。在多个文件里搜关键词时特别高效。--include限定文件类型。比如grep -r root /etc --include*.conf只在 conf 文件里搜索。关于性能我踩过一个坑。需要在大量日志文件中搜索时不要直接cat *.log | grep xx而是用grep -r配合多个文件参数让它自己递归扫描省掉一次多余的文件读取grep -rn --include*.log ERROR /var/log/app/另外要留意grep 适合“找行”不适合“按字段统计”。如果只是筛选出符合某种模式的行grep 是最快的但如果需要对筛选结果做字段求和、去重统计就不必硬用 grep 在输出上拼凑而是把它和 awk、sort、uniq 组合起来。这个组合思路后面会专门讲到。3.4 grep 常见坑正则符号在 shell 里的转义新手最容易犯的错是把正则和 shell 通配符混在一起用。比如要匹配日志文件里所有以.log结尾的行有人会写grep *.log access.log这里*在正则里表示“前一个字符重复 0 次或多次”所以*.log实际匹配的是“任意个*字符后接任意字符再接 log”效果完全不是你想的*通配。正确的写法是把.转义成字面量grep \.log access.log同理搜索带引号的内容时在 shell 里会被当成字符串边界最好用单引号包裹正则避免 shell 先做变量展开和转义解读。比如匹配包含双引号的请求行直接写grep GET access.log是可以的但如果你用的是双引号包裹正则里层双引号就得写成\grep GET access.log # 单引号包裹安全 grep \GET access.log # 双引号包裹需要转义我不建议大家记什么“到底该几个反斜杠”最简单的方式就是正则尽量用单引号包起来如果正则内部还需要单引号字符再单独处理。4. sed流编辑器按行做“增删改”4.1 sed 的工作流程grep 只负责“挑”想对文本内容做替换、删除、插入就要请出 sed 了。sed 是一种流编辑器它不会直接修改源文件而是把输入流逐行读入内存执行你给出的命令后把处理结果输出到标准输出。默认情况下源文件是安全的除非你用了-i选项。这个“逐行处理”的机制特别重要决定了 sed 写法的思考方式你不是“针对某个文件改”而是“对每一行判断是否命中规则命中就做动作没命中就原样输出”。用命令行演示最直观sed s/200/999/ /tmp/text-lab/access.log这条命令里s是“替换”动作格式是s/旧内容/新内容/。它把每一行中第一次出现的200换成了999但文件本身没变。这只是 sed 的一个最小例子。4.2 地址匹配与动作组合sed 真正强大的地方在于地址匹配。语法是sed 地址动作 file地址可以是一行号、一个范围、一个正则。组合起来可以做许多精确操作sed -n 2p access.log # 只打印第 2 行-n 禁止默认输出 sed -n 2,4p access.log # 打印第 2 到 4 行 sed -n /500/p access.log # 打印包含 500 的行 sed /500/d access.log # 删除包含 500 的行 sed 1,3d access.log # 删除第 1 到 3 行 sed s/192.168.1.10/10.0.0.1/ access.log # 替换 IP这里要说一下-n的配合逻辑。默认 sed 会打印所有行即使你执行d删除动作后其他行也会照常输出。如果你只想看被匹配到的行就必须用-n抑制默认输出再配合p命令显式打印目标行。实际使用时我经常把地址范围跟正则搭配sed -n /2024:13:55/,/2024:14:02/p /tmp/text-lab/access.log这条命令会从匹配“13:55”的行开始打印直到匹配“14:02”的行结束非常适合按时间段截取日志片段。这个技巧在日志分析里相当实用比用 grep 一条条匹配效率高很多。4.3 实战批量替换配置文件里的 IP 和端口运维工作中最常见的需求是把一套环境的配置批量移植到另一套环境。比如一个配置目录下有几十个application.yml里面写了旧 IP192.168.1.10和端口8080要换成新 IP10.12.5.8和新端口9090。用 sed 可以一条命令完成全目录替换find /etc/myapp -name *.yml -exec sed -i s/192\.168\.1\.10/10.12.5.8/g; s/8080/9090/g {} \;这里我用了两个关键点s/旧/新/g中的g表示“每一行所有匹配都替换”不加的话只替换每行第一次出现的位置-i后面接没有备份后缀表示直接写回原文件。稳妥来说我建议写成-i.bak这样 sed 会在修改前复制一份.bak备份文件出问题时能快速回滚。为什么正则里的点要写成\.因为在正则里.是“匹配任意字符”的通配符如果不转义192.168.1.10可能把192x168y1z10也会匹配上。这种时候宁可多写两个反斜杠也不要省。4.4 sed -i 的安全操作与备份习惯关于-i我想专门多说几句。我身边的同事好几次因为忘了备份一条sed -i把线上配置改错整夜都在回滚。所以我的习惯是sed -i.bak s/old_ip/new_ip/g /etc/nginx/nginx.conf这样会在同目录生成nginx.conf.bak。确认改动无误后再决定是否删除备份。另一个安全习惯是先不带-i跑一遍观察输出是否符合预期确认无误后再加-i执行。如果配置文件里包含中文注释用 sed 替换英文内容通常没问题但如果你要替换的段落跨越行或者包含特殊符号如、/等很容易踩坑。比如替换内容里包含/分隔符就会被错误解释。解决办法是换用另一个分隔符比如#sed -i s#/old/path#/new/path#g /etc/some.conf这个技巧就叫“换分隔符”记住了能少掉很多头发。5. awk按列统计文本处理的天花板5.1 awk 的分行分列模型awk 与 grep、sed 的最大不同在于它默认按**空白字符空格/Tab**把每一行切分成多个字段。$0代表整行$1代表第一个字段$2代表第二个字段以此类推。这种模型用来处理“每一列有明确含义”的文本非常顺手。比如我们测试日志里$1是 IP$4是时间$6是请求方法$7是请求路径$9是状态码$10是字节数。直接用awk {print $1}就能把 IP 列全部打印出来awk {print $1} /tmp/text-lab/access.logawk 还允许自定义字段分隔符。-F参数配合正则可以处理 CSV、日志等非空白分隔的数据。比如解析/etc/passwd它是冒号分隔的awk -F: {print $1, $3} /etc/passwd这条命令同时打印用户名和 UID是系统管理里的高频操作。5.2 BEGIN/END 和 pattern {action} 结构awk 的完整语法格式是awk BEGIN{初始化动作} pattern{逐行动作} END{收尾动作}BEGIN块在处理任何输入行之前执行常用于定义变量、打印表头pattern {action}是逐行处理的核心只有条件匹配时才执行动作END块在所有行处理完毕后执行常用于输出汇总结果。光看结构可能觉得抽象用求和统计马上就能理解。统计一下日志中状态码 200 的请求一共多少字节awk $9 200 {sum $10; count} END {print count:, count, bytes:, sum} /tmp/text-lab/access.log这里$9 200是 pattern表示只处理第 9 列为 200 的行{sum $10; count}是动作对字节数累加END最后把统计结果打印出来。awk 内部不需要管变量是否已定义默认值就是数字 0 或空字符串。5.3 实战统计接口平均响应时间、按 IP 聚合流量下面用一个真实场景把 awk 的能力串起来。假设你的 nginx 日志里记录了每个请求的响应时间字段在$NF最后一个字段你想按接口路径统计平均耗时和请求次数awk {path[$7]; sum[$7]$NF} END {for (p in path) print p, path[p], sum[path]/path[p]} access.log这条命令需要用一段带响应时间的日志来测原理是path[$7]建立以“第 7 列路径”为 key 的请求次数计数器sum[$7]$NF把每个请求的响应时间累加到对应的路径 key 下END里遍历所有路径输出次数和平均值。这个模式是 awk 做聚合分析的核心套路往上套业务几乎通用。按 IP 聚合流量也是同一个思路只要把 key 换成$1即可awk {ip[$1]; bytes[$1]$10} END {for (i in ip) print i, ip[i], bytes[i]} /tmp/text-lab/access.log | sort -k3 -nr加上sort -k3 -nr后结果会从流量最大的 IP 往下排列一眼就能看出谁在“吃”带宽。5.4 awk 与 sed/grep 的搭档玩法三个工具组合起来能解决很多看似复杂的任务。比如我想提取访问/api/login的 500 请求率可以先用 grep 筛出该接口的日志再用 awk 统计grep /api/login /tmp/text-lab/access.log | awk {total; if ($9 500) bad} END {print total:, total, bad:, bad0, rate:, (bad/total*100)%}这里bad0是为了在 bad 未初始化时输出0而不是空值。(bad/total*100)算出百分比。三条命令的组合比单独用任何一个都要灵活也是我实际排查问题最常用的方式。6. 多工具组合一套完整的日志分析实操6.1 场景设定纸上谈兵讲完了还是用一份真实的 nginx 请求日志把整个流程走一遍。职责是找出最近一天访问量最高的 Top 5 接口、各接口平均耗时、以及 5xx 错误的分布。这是日志分析里最经典的需求之一。环境还是用刚才生成的/tmp/text-lab/access.log。先直接跑一个综合脚本cat /tmp/text-lab/access.log | awk {print $7, $NF, $9} | grep -v /static /tmp/text-lab/result.txt这里我把每行精简成“路径 耗时 状态码”三列排除了静态资源。但这一步有多余——awk 本身就能过滤字段值如果路径是第 7 列且等于/static直接用 pattern 排除更干净awk $7 !~ /\/static/ {print $7, $NF, $9} /tmp/text-lab/access.log /tmp/text-lab/result.txt这个写法用!~表示“不匹配”。注意正则里/需要转义写成\/static。养成“能用 awk 过滤就不过多起管道”的习惯能省去很多中间管道开销。6.2 统计 Top 5 接口拿到精简结果后用 sort 和 uniq 统计路径出现次数awk {print $1} /tmp/text-lab/result.txt | sort | uniq -c | sort -nr | head -5这里的逻辑是awk {print $1}把路径列提取出来sort排序保证相同的路径相邻排列uniq -c统计连续相同行的数量sort -nr按数字倒序排列head -5取前五。执行结果可以直观看到哪个接口被调用最多。这里要注意uniq -c只能统计相邻重复行如果文件没有被 sort 到相邻统计结果会分成多行所以必须先 sort 再 uniq。6.3 统计每个接口的平均耗时继续用结果文件按接口名聚合平均耗时awk {sum[$1]$2; cnt[$1]} END {for (i in sum) print i, sum[i]/cnt[i], cnt[i]} /tmp/text-lab/result.txt | sort -k2 -nr这份统计里第 1 列是路径第 2 列是耗时第 3 列是请求次数。sort -k2 -nr表示按第二列数字倒序排列能看出哪些接口耗时最多。如果发现静态资源或某接口响应时间异常高再单独用 grep 拉出该路径的样本行查看具体请求细节。6.4 定位 5xx 错误分布错误分布用一条 awk 就能搞定awk $3 500 {print $1, $2} /tmp/text-lab/result.txt | sort | uniq -c但我们要的不只是数量最好能看出是哪个 IP 触发的。回到原始日志去关联awk $9 500 {print $1, $7} /tmp/text-lab/access.log | sort | uniq -c这个输出把 IP、接口、错误次数列出来可以快速锁定“哪个 IP 在反复打某个接口导致 5xx”。如果需要更完整的上下文可以再 grep 出这些 IP 的全部日志用 sed 或者 head/tail 截取时间范围。6.5 把流程写成小脚本多次操作之后我发现把这些命令存在一个 shell 脚本里非常值。以后每次排查看日志直接跑一句脚本不用重新敲命令#!/bin/bash LOG${1:-/var/log/nginx/access.log} echo Top 10 interfaces: awk {print $7} $LOG | sort | uniq -c | sort -nr | head -10 echo echo 5xx error distribution: awk $9 500 {print $1, $7} $LOG | sort | uniq -c | sort -nr echo echo Response time top 10: awk {print $7, $NF} $LOG | awk {if ($2 ~ /^[0-9.]$/) print} | sort -k2 -nr | head -10这里第 3 段加入了一个正则判断$2 ~ /^[0-9.]$/用来过滤非数值耗时的脏数据避免 sort 按字符串排序时得到错误顺序。脚本里可以直接用位置参数传日志路径默认指向 nginx 访问日志非常实用。7. 常见问题与排查技巧速查表7.1 工具输出不显示中文碰到中文乱码时大多数情况不是工具本身的问题而是 locale 环境不对。先检查echo $LANG如果输出不是en_US.UTF-8或zh_CN.UTF-8在命令行临时执行export LANGen_US.UTF-8再试。另外 grep 匹配中文时不要用grep 中文 file直接匹配先确认文件编码一般 UTF-8 没问题若是 GBK 编码则需要先转码iconv -f GBK -t UTF-8 file.log | grep 中文7.2 正则匹配不到结果这个是最常见的问题通常有四个原因原因现象解决正则写错明明字符串在文件里却匹配不到用grep -F做字面量匹配测试转义问题.匹配了不该匹配的字符需要匹配字面量点号时写成\.大小写文件里是 ERRORpattern 写 error用-i忽略大小写变体编码文件含 CRLF先dos2unix转换或用sed -i s/\r$//排查时最好的调试手段是用grep -o搭配更宽松的正则逐步缩小范围。比如先确认能否匹配到一个短字符再扩展成完整模式。7.3 大文件处理卡顿服务器上的日志动辄几个 GB直接cat再管道会白白耗内存。正确姿势是直接用 grep/sed/awk 读取让它们自己流式处理。如果只是要快速看文件开头用head找行数用wc -l找某一个模式用grep -n。避免在管道前面加不必要的cat。对于几个 GB 文件还可以用 awk 直接处理配合NR行号变量定位特定行范围比装个大编辑工具快得多。比如提取第 1000 到 2000 行awk NR 1000 NR 2000 huge.log另一个方法是用sed -n 1000,2000p huge.log两者都可但 awk 可扩展性更强。7.4 隐藏字符与编码问题排查配置时经常遇到“看起来一样却比对不上”的问题。用sed -n l file可以把不可见字符按可见形式展示出来sed -n 1l /tmp/text-lab/access.logl命令会把 Tab 显示成\t行尾显示成$方便检查是否有 Windows 换行符。如果发现\r用sed -i s/\r$// file统一清除。处理 CSV 文件时我习惯先用file命令确认编码和换行符避免后面 awk 的-F,解析时被\r干扰file data.csv如果输出带with CRLF line terminators先转一下再处理。7.5 别忽略流式处理的最小化原则最后分享一个能提高效率的习惯。多工具组合很强大但也要避免“无脑起管道”。能在一个 awk 里完成的统计不要拆成 5 条 grep 加 3 条 sed。管道越多排查问题越困难性能也越差。一个实用的判断标准是如果整条命令超过 3 个管道大概率可以用 awk 单条实现。8. 一点个人使用体会我这些年大量使用 Linux 文本处理工具排查线上问题最大的感受是这些命令看起来“古老”但它们的处理模型非常稳定——按行读入、规则匹配、流式输出永远不依赖界面和图形环境。SSH 登录上去就能干活配合脚本还能自动化重复操作。遇到复杂需求时先用 grep 快速筛选范围再用 sed 修正文本最后用 awk 统计聚合这种“三步走”的思路几乎覆盖了 90% 的运维场景。如果你刚学我建议不要盯着满屏参数背而是把今天文章里的日志分析脚本在真实环境里跑一遍改一改路径和字段观察输出变化。真正上手一次比读十遍理论都管用。最后再提一个小技巧写命令时尽量用双引号把变量括起来比如grep $keyword file否则文件路径或关键词里带空格时命令会突然失效。这些细小的习惯积累起来就是处理文本时少踩坑的关键。
阅读完成 · 觉得有帮助?
咨询建站