ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux文本处理三件套:grep、sed、awk实战指南

Linux文本处理三件套:grep、sed、awk实战指南 1. 第12篇该讲什么绕不开的文本处理三件套写这个系列到现在前面那十一篇已经把Linux的入门骨架搭得差不多了——文件与目录操作、权限管理、用户与组、磁盘管理、进程管理、软件包安装、网络基础、服务管理、日志查看、Shell脚本基础、系统性能监控。如果你一路跟下来会发现一个规律这些命令单看都能理解真正干活的时候却总觉得差点意思。差在哪差在处理文本的能力。Linux的设计哲学里有一条核心原则一切皆文件而文件和命令的输出本质上大多是文本流。你查一个服务状态得到的是文本看一条日志记录是文本解析配置文件还是文本。能不能把这些文本快速、精确地拆解、筛选、统计、替换直接决定你排查问题的速度。很多运维老手看起来手速快快就快在grep、sed、awk这三个命令用得炉火纯青三行命令解决别人半小时的手工活。所以这第十二篇我决定用一整篇的篇幅来聊Linux文本处理的三驾马车——grep、sed、awk。这三个命令单独拆开每一个都能写一本书我这篇不追求面面俱到而是挑实际工作中最高频的场景把它们的核心用法和组合套路讲透。看完这篇你再去看日志、配脚本、处理数据文件体感会完全不一样。适用读者已经熟悉Linux基本操作、想进一步提升日常效率的开发者和运维工程师。如果你刚开始接触Linux建议先把我前面几篇基础命令刷一遍再回来看这篇不然管道符和正则叠加起来容易懵。2. grep从搜得到到搜得准2.1 最基础的搜索为什么总是不够用绝大多数人用grep就是两条grep error app.log grep error -r ./src/能跑但远远不够。真实场景里你遇到的问题往往是这样的日志文件几个GB直接grep error刷屏刷到怀疑人生报错日志的关键信息不在同一行需要把上下文一起拉出来你明明记得配置里有某个IP但那个IP前面可能带着端口号、后面可能跟着路径死板的关键词匹配根本搜不到。grep本身的设计初衷就是全局正则表达式打印它的核心是正则匹配不是简单字符串查找。理解这一点你的搜索能力会上一个台阶。2.2 高频参数逐个说先过一遍我日常使用率最高的几个参数后面的实战案例全都会用到# 扩展正则避免一堆反斜杠转义 grep -E pattern file # 显示匹配行前后N行上下文排查日志必备 grep -B 5 -A 10 OutOfMemory app.log # 只要文件名不要匹配内容批量判断时非常高效 grep -l error *.log # 反向匹配排除掉某些噪音数据 grep -v ^# redis.conf # 统计匹配行数 grep -c ERROR app.log # 只输出匹配的部分而不是整行提取IP、时间戳利器 grep -o 192\.168\.[0-9.]* app.log # 忽略大小写 grep -i error app.log有几个点值得展开说。grep -E是我个人的习惯用法。基础正则里、?、|、()这些字符都需要反斜杠转义才能生效写起来又丑又容易错。加上-E之后直接按扩展正则的语法写心智负担小很多。比如搜多种关键词# 不推荐又是转义又是管道符看着头大 grep error\|exception\|failed app.log # 推荐干净利落 grep -E error|exception|failed app.loggrep -o这个参数知道的人不少但用得好的人不多。它的意义在于把匹配到的内容从整行内容里剥离出来。举个例子从访问日志里提取所有IPgrep -oE ([0-9]{1,3}\.){3}[0-9]{1,3} access.log | sort | uniq -c | sort -rn | head -20这一条命令的组合思路后面会细讲但核心就是-o把IP从长行里抠出来后续的排序统计才有意义。没有-o的话一个IP出现一次就输出一整行日志uniq -c统计出来的数字就全错了。2.3 实战日志级别统计和上下文抓取假设现在有一个典型的Java服务日志格式类似2025-01-15 10:23:45.123 [http-nio-8080-exec-3] ERROR com.example.OrderService - 订单创建失败你要做的事是第一步统计各个级别的日志数量。grep -c ERROR app.log grep -c WARN app.log grep -c INFO app.log这里有个认知偏差需要纠正grep -c统计的是匹配的行数不是匹配的次数。如果一行里出现三次ERRORgrep -c只计数一次。想统计出现次数得用grep -o | wc -lgrep -o ERROR app.log | wc -l日常工作里统计日志级别数行数就够用了。但如果你想统计某个异常关键字在全文里总共出现多少次就必须用第二种写法。第二步抓取某一次报错的完整上下文。日志里真正的异常堆栈不是一行而是一大段。只搜关键信息那一行完全不够看你得把前后文拿出来grep -B 2 -A 30 订单创建失败 app.log | tail -80-B 2看异常发生前两行的业务上下文-A 30把堆栈信息完整拉出来。排错的时候这一条命令能让你少翻很多屏幕。第三步排除干扰项。很多日志里同一关键字既出现在正常分支也出现在异常分支直接搜会混入大量噪音。比如你知道某个定时任务每天都会打印一条包含error的正常日志路径是/tasks/scheduler那就可以排除它grep error app.log | grep -v schedulergrep和grep之间的管道协作是后面要讲的三件套组合拳的雏形。记住一个思维grep负责筛选行多个grep串联就是层层缩小范围。2.4 一个容易忽略的坑管道里的实时输出用tail -f追日志的时候直接加grep会导致输出被缓冲日志半天不刷新# 等半天看不到新日志其实进程在跑是缓冲区没刷 tail -f app.log | grep ERROR # 加--line-buffered每行都刷 tail -f app.log | grep --line-buffered ERROR这个坑我踩过不止一次排查线上问题时盯着屏幕以为服务没在打日志实际是grep的管道缓冲区在作怪。加了--line-buffered之后行为就符合直觉了。3. sed流式编辑器的三板斧如果说grep是筛选器那sed就是编辑器。它最大的特点是不用打开文件就能完成编辑操作而且在处理大文件时几乎不占内存——因为它是流式处理的一行一行读进来、处理完、输出不把整个文件一次性加载到内存里。3.1 定位与动作sed的核心心智模型sed的语法看起来唬人拆穿了就两个部分定位和动作。sed 定位命令 文件定位决定对哪些行动手动作决定做什么事。常见的定位方式有数字行号3表示第3行3,10表示第3到10行正则定位/pattern/表示匹配到该模式的所有行特殊定位$表示最后一行1~2表示从第1行开始每隔2行也就是奇数行常见的动作有p打印d删除s替换a在行后追加i在行前插入把定位和动作组合起来就构成了sed的全部变化。这个模型一旦建立你会发现sed一点都不难。3.2 删除和替换日常工作最常用删除空行和注释行这是清理配置文件最经典的操作# 删除空行 sed /^$/d nginx.conf # 删除以#开头的注释行 sed /^#/d nginx.conf # 组合使用先删注释再删空行 sed /^#/d;/^$/d nginx.conf注意sed默认不会真的改文件它只是把处理结果输出到终端。想直接改文件要加-i参数。我这里故意先不加是为了引出下面的讨论。替换操作的核心场景是把旧版本号批量换新、把测试环境的域名批量改成生产域名# 把每一行的第一个localhost换成api.example.com sed s/localhost/api.example.com/ config.properties # 加g表示全局替换不限于每行第一个 sed s/localhost/api.example.com/g config.properties # 使用符号引用匹配到的内容做前后缀增强 sed s/[0-9]\{4\}/[]/g version.txt第三个例子值得解释一下[0-9]\{4\}匹配四位数字在替换部分代表匹配到的这四位数字结果是每个四位数字都被加上方括号。这在批量格式化输出的时候非常实用。3.3 在指定行前后插入内容有时候不是要改已有内容而是要在特定位置新增配置。比如Nginx配置里要在server {}块内加一行限流配置# 在匹配到server {的行之后追加一行配置 sed /server {/a\ limit_req zoneapi burst10; nginx.conf这里a\是在匹配行之后追加的动作反斜杠后面的缩进和内容会被原样插入。如果你要在匹配行之前插入用i\动作sed /location \/api/i\ proxy_set_header X-Source sed-demo; nginx.conf这类操作手动去编辑文件也不难但在批量改多台服务器的配置时sed两秒钟全部搞定这就是它的价值所在。3.4 原地修改的坑必须加备份后缀前面反复强调sed默认不修改原文件一旦你确认命令没问题想真正落盘用-i# 直接修改文件GNU sed sed -i s/old/new/g app.properties # 推荐做法修改前先生成备份文件 sed -i.bak s/old/new/g app.properties强烈建议你用第二种写法。-i.bak会在修改前把原文件复制成app.properties.bak即使替换规则写错了也能一条命令恢复回来mv app.properties.bak app.properties特别是线上环境批量改配置文件的时候备份是你的后悔药。别嫌多此一举我在生产环境用sed改坏过配置文件导致服务起不来幸好有备份一分钟就恢复了。这种教训犯一次就够。另一个小坑在管道中处理流时不要加-i。你不能写cat file | sed -i s/a/b/因为-i只能作用于文件不能作用于标准输入流。这时候直接写sed s/a/b/ file拿到输出结果后再重定向到新文件即可。4. awk名字叫文本处理实际是个迷你编程语言4.1 awk和grep/sed的本质区别awk和前面两个命令最大的不同在于它不是简单的行处理工具而是列处理工具内建变量、条件判断、循环、数组、自定义函数本质上是一门小型编程语言。awk默认按空白字符空格和Tab把一行拆分成多个字段分别存在$1、$2、$3……里$0代表整行。这个设计让它特别适合处理解析类的任务——日志分析、数据报表、CSV文件整理。4.2 字段处理最常用的awk场景先看几个能直接抄作业的例子。提取指定列# 打印passwd文件的第一列用户名和最后一列登录Shell awk -F: {print $1, $NF} /etc/passwd-F:指定冒号作为字段分隔符因为passwd文件的字段是按冒号分隔的。$NF是awk内置变量代表这一段有几个字段就取第几个也就是最后一列。这是一个非常常用的trick——当你不知道一行有多少字段、只想取最后一段时$NF就是答案。条件过滤列# 找出UID大于1000的用户 awk -F: $3 1000 {print $1, $3} /etc/passwd这和grep最大的区别是grep只能按整行内容做筛选awk可以按某个具体字段的数值做逻辑判断。$3是UID列 1000是条件满足的才执行后面的打印。格式化输出# 使用printf做对齐输出 awk -F: {printf %-20s %s\n, $1, $NF} /etc/passwd%-20s表示左对齐宽度20的字符串输出会变得非常整齐。这在生成报告时比简单的print好用得多。4.3 BEGIN和END处理前和处理后awk的完整结构是awk BEGIN{...} {每行处理} END{...}。BEGIN块在处理第一行之前执行适合初始化变量、打印表头END块在处理完所有行之后执行适合输出统计结果。我最早被awk圈粉就是因为一条统计命令。假设access.log每行格式是IP 时间 请求路径 状态码 响应时间我想知道平均响应时间awk {sum $5; count} END {printf 平均响应时间: %.2fms\n, sum/count} access.log读每一行累加第5列行数加1全部读完输出平均值——这个逻辑用其他语言写至少五到六行awk一行搞定。再比如找响应时间最长的请求awk {if ($5 max) {max $5; line $0}} END {print 最慢请求耗时:, max; print 对应日志:, line} access.logmax变量初始值是0每行比较当前响应时间是否更大是就更新max并记下整行。走完所有行后最慢请求和它的完整日志就拿到了。4.4 计数与去重awk替代sort|uniq的写法统计日志里各个状态码出现的次数传统写法是grep -o加sort加uniq -c用awk其实更简洁awk {count[$9]} END {for (code in count) print code, count[code]} access.log这里count[$9]用状态码作为数组下标每出现一次该状态码对应的计数加1。END块里for (code in count)遍历数组输出结果。注意awk数组的输出顺序是哈希序不是状态码大小序想排序可以在管道里再接sort -n。这个数组思路还能用来统计日志里排名前10的错误信息awk {count[$0]} END {for (msg in count) print count[msg], msg} error.log | sort -rn | head -10一行一行的错误日志内容被当作数组下标重复的错误自动聚合然后再用sort做排名。这个组合我在排查线上哪个错误最多时反复用省了无数手工翻日志的时间。4.5 字符串函数不只会数数awk内置的字符串函数也相当实用。比如提取时间字段里的日期部分# 假设$2是2025-01-15T10:23:45用substr取前10位 awk {print $1, substr($2, 1, 10)} app.log或者把某列里的引号去掉awk {gsub(//, , $4); print $0} data.csvgsub是全局替换把第4列里的所有双引号替换成空字符串。这种清洗数据的操作在处理从外部系统导出的文件时非常常见。5. 三件套组合真正的生产力在管道里单独学每个命令效果有限。工作时真正给你省时间的是把grep、sed、awk用管道串起来一条命令解决一个完整的业务问题。这一节我用四个真实的排查场景来讲组合套路。5.1 场景一找出访问量TOP 10的IPNginx访问日志里第一个字段是客户端IP。你要找出攻击最猛的或者访问最频繁的IPawk {print $1} access.log | sort | uniq -c | sort -rn | head -10拆开看awk把每行的IP单独拎出来sort把相同IP聚到相邻位置uniq -c对相邻的相同行做计数sort -rn按计数从大到小排head -10只取前10。五个命令各司其职这就是Linux管道的精髓——每个命令做好一件事组合起来完成复杂任务。5.2 场景二统计接口的平均响应时间并只看某个接口另一个日志分析场景。日志格式是IP 时间 GET /api/order HTTP/1.1 200 356关注的是每个接口的平均耗时awk $4 ~ /\/api\/order/ {sum $5; count} END {if (count 0) printf avg: %.2fms (count%d)\n, sum/count, count} access.log先用$4 ~ /pattern/做正则匹配过滤出目标接口再累加耗时。这里有个容易踩的坑$4是带引号的完整请求行比如GET /api/order HTTP/1.1正则写/\/api\/order/时正斜杠需要转义成\/否则awk会把正则分隔符搞混。5.3 场景三清理配置文件里的注释、空行和行尾空格把一份冗长的配置文件瘦身成干净版本sed /^$/d; /^#/d; s/[[:space:]]*$// nginx.conf nginx-clean.conf[[:space:]]*$匹配行尾的所有空白字符s///把它们替换为空。三条规则用分号串联在同一个sed命令里输出重定向到新文件。不破坏原文件、又能产出一份干净版本这个模式我经常用来生成对比配置。5.4 场景四在指定行号范围之间提取内容比如要提取配置文件里从server {开始到第一个}结束之间的全部内容sed -n /server {/,/}/p nginx.conf-n是不自动打印每一行的参数/server {/,/}/是行号范围从匹配server {的行开始到匹配}的行结束p动作把这段范围的内容打印出来。这套范围地址语法是sed里非常高级的玩法特别适合提取日志里某次报错从发生到结束的完整段落。5.5 组合拳心得一点点加复杂度我看到很多初学者组合命令时一上来就想挑战十连环管道结果中间某个环节输出格式变了后面全乱套。正确的方式是一层一层加。先单独跑awk {print $1} access.log确认输出的确是IP再加| sort确认排序正常再加| uniq -c确认计数正确……每一步都验证了再往下加。等整条管道跑通再把它固化成脚本或alias以后一条命令复用。这套渐进式构建管道的心法比背任何命令参数都重要。6. 正则表达式速查三件套的共同地基三个命令虽然功能不同但正则语法是它们共同的语言。很多人在这一步卡住——命令参数背得溜一写正则就翻车。这里把高频的正则符号整理成一张表方便你从零开始建立肌肉记忆。符号含义示例匹配效果.任意单个字符a.cabc、a1c都能匹配*前一个字符重复0次或多次ab*cac、abc、abbc都匹配前一个字符重复1次或多次abcabc匹配ac不匹配?前一个字符出现0次或1次ab?cac、abc匹配^行首^#匹配以#开头的行$行尾error$匹配以error结尾的行[...]字符集合[0-9]匹配任意一位数字[^...]排除集合[^0-9]匹配任意非数字字符|或需-E或转义a|b匹配a或b\(\)分组需-E或转义\(ab\)匹配一个或多个ab\{m,n\}重复次数需-E或转义[0-9]\{2,4\}匹配2到4位数字在grep和sed里基础正则BRE要求对、?、|、()、{}用反斜杠转义加上-E后用扩展正则ERE就不用转义了。awk默认使用扩展正则所以写awk正则时直接写、|就行。一个实战练习帮你巩固提取日志中所有形如ERROR-20250115的错误码# grep -E写法 grep -oE ERROR-[0-9]{8} app.log | sort | uniq -c # awk写法 awk /ERROR-[0-9]{8}/ {print} app.log | head两条命令输出结果一样但思路不同grep倾向于找出符合条件的行或内容awk倾向于按列和模式做进一步处理。弄清楚它们的分工你会发现很多任务其实可以殊途同归选自己顺手的那条路就好。7. 字符集与编码最容易翻车的隐藏变量文本处理命令跑出乱码、正则匹配不到中文、文件内容看着没问题却死活匹配不中——这些问题十有八九出在字符集和编码上。7.1 locale对grep和awk的影响Linux环境下grep、awk的匹配行为受locale环境影响。如果你的系统locale是en_US.UTF-8处理纯ASCII字符没问题但遇到中文日志时某些版本的grep会按字节匹配而不是按字符匹配可能导致正则里的.匹配到半个汉字。最直接的现象就是你以为.能匹配一个中文字符实际它匹配了一个字节结果输出乱码或漏匹配。排查方法很简单locale如果LANG是C或POSIXgrep对多字节字符的处理会比较原始。建议把locale设置成UTF-8export LANGen_US.UTF-8 export LC_ALLen_US.UTF-87.2 文件编码不一致的快速检测两个文件内容看起来一样但一个UTF-8一个GBKgrep匹配中文就是匹配不上。用file命令快速检测编码file -i app.log输出的charset字段会告诉你文件实际编码。如果需要把GBK文件转成UTF-8再处理iconv -f GBK -t UTF-8 app.log app-utf8.log7.3 大文件扫描时的性能小技巧处理几个GB的日志时grep会明显变慢。这时把locale临时切成C能带来可观的性能提升因为Clocale下grep可以做纯字节比较跳过UTF-8的多字节字符解析LC_ALLC grep ERROR huge.log我实测过大文件场景LC_ALLC grep比默认locale快接近一倍。代价是正则里的字符类比如[[:alpha:]]只会匹配ASCII字符但排查英文日志时完全够用。这个技巧属于知道的人看一眼就懂不知道的人踩坑半小时的典型。7.4 一个隐蔽的错误CRLF行尾从Windows拷贝过来的文件每行结尾是\r\n而不是\n。直接grep或sed处理时$锚点匹配到的内容是\r前面看起来一切正常但你要是做行尾处理或者脚本逻辑判断就会发现莫名其妙多出一个不可见字符。遇到这种情况先清洗sed -i s/\r$// config.conf把每行结尾的\r删掉后续处理就正常了。这也是为什么我处理外部导入的文件前会习惯先跑一次file确认格式。8. 一条命令的演进从查日志到自动巡检脚本工具讲完了最后用一个完整案例把这套东西串起来——写一个简单的日志自动巡检小脚本。这个脚本做的事情很朴素扫描指定日志文件找出最近一段时间内的ERROR条目、统计出现次数最多的错误信息、输出TOP 5帮助值班人员在早上快速了解夜间发生了什么。先写核心的查找逻辑#!/bin/bash LOG_FILE${1:-/var/log/app/app.log} YESTERDAY$(date -d yesterday %F) TODAY$(date %F) echo 错误日志统计$YESTERDAY 至 $TODAY # 统计级别为ERROR的行数 ERROR_COUNT$(grep -c ERROR $LOG_FILE) echo 错误总行数: $ERROR_COUNT # 提取错误信息中括号里的业务错误码统计TOP 5 grep ERROR $LOG_FILE \ | grep -oE \[[A-Z0-9_]\] \ | sort | uniq -c | sort -rn \ | head -5 \ | awk {printf %-6s %s\n, $1, $2}这个脚本里有个细节值得注意grep -oE \[[A-Z0-9_]\]提取的是类似[ORDER_CREATE_FAILED]这样的错误码。[和]在正则里本身有特殊含义字符集合所以要用\[和\]转义。这就是正则的典型翻车点——你搜索的内容里如果含有正则元字符必须先转义。脚本跑出来的输出长这样 错误日志统计2025-01-15 至 2025-01-16 错误总行数: 187 55 [ORDER_CREATE_FAILED] 32 [PAY_TIMEOUT] 18 [DB_CONNECTION_POOL_EXHAUSTED]看到这个输出值班人员就能立刻知道夜间主要发生了什么类型的问题再针对性地用grep -B 2 -A 20 PAY_TIMEOUT展开详情。这些命令单独看都平平无奇组合起来就是一个实用的巡检工具。我再补充一个更进阶的动作定期用这个脚本扫描日志把结果追加到一个汇总文件里形成趋势数据grep ERROR $LOG_FILE \ | grep -oE \[[A-Z0-9_]\] \ | sort | uniq -c | sort -rn | head -1 \ | awk -v today$(date %F) {print today, $1, $2} error_trend.tsv每天跑一次error_trend.tsv里就会积累一份当天最高频错误及其次数的历史数据。哪天你发现某个错误码的数字突然飙升基本就是变更或者流量异常的信号——这种基于文本日志的简易监控完全不依赖任何外部组件但效果出奇好。9. 几个我踩过的坑希望你绕开9.1 引号选择为什么会命令明明对但结果不对单引号和双引号在Linux命令里有本质区别。双引号内的$、反引号等特殊字符会被Shell解释单引号内则原样保留。写正则时强烈建议用单引号把pattern包起来否则Shell可能抢先做变量展开# 错误双引号里$匹配到的是Shell变量 awk {print $1} access.log # $1被Shell当成空变量awk收到的是print # 正确单引号保护$1不被Shell解释 awk {print $1} access.log同样的道理如果你要在sed的替换部分里引用Shell变量反而需要用双引号# 使用Shell变量做替换内容必须用双引号 sed s/localhost/$HOST_NAME/g nginx.conf什么时候用单引号、什么时候用双引号这个问题本质上取决于你希不希望Shell先处理一遍。正则和awk代码用单引号需要变量注入时用双引号。这个规则弄清楚能少踩三分之一的坑。9.2 不要对同一个文件边读边写新手最常犯的错误之一# 错误示例输出重定向覆盖了源文件 sed s/old/new/g file.txt file.txtShell执行重定向时会先创建并清空file.txt然后sed才开始读文件——此时文件内容已经是空的了处理结果自然也是空的。这个坑害人无数文件瞬间被清空连后悔的机会都没有。正确做法是输出到临时文件再覆盖sed s/old/new/g file.txt file.tmp mv file.tmp file.txt或者用我们前面提到的-i.bak方案安全性和原子性都有保障。同样的逻辑适用于任何命令的重定向——先确认输入输出不是同一个文件。9.3 GB级日志的性能焦虑真的不用慌很多人一听说处理几个GB的日志就想着要不要上Spark、上Flink。大部分场景真的不用。grep、sed、awk都是流式处理内存占用极小跑几GB的文件就几十MB的内存耗时取决于磁盘IO。实测经验grep扫一个3GB的日志找关键字普通SSD上大概20到40秒。单次排查完全能接受。真正需要优化的是反复扫描同一个大文件的场景。比如巡检脚本每天要跑好几个grep同一个文件那建议先做一次裁剪只保留需要的时间窗口# 先把今天的日志切出来后续操作全部基于小文件 sed -n /2025-01-16 00:00:00/,/2025-01-16 23:59:59/p huge.log today.log后续的统计、提取、分析都在today.log上跑速度能快一个数量级。所谓性能优化很多时候不是换工具而是减少被处理的数据量。9.4 小心正则中的贪婪匹配陷阱sed的正则替换默认是贪婪的——它会尽可能匹配最长的内容。看这个例子想把title2025/title里的数字提取出来# 事与愿违因为.*会尽量多匹配$1匹配的是2025 echo title2025/title | sed -E s/.*([0-9]).*/\1/.*是贪婪的第一个.*会一直吃掉到最后一个导致后面根本不再有.*可匹配结果这个正则直接匹配失败输出原样。解决办法是让.*变成非贪婪——在GNU sed里用[^]*代替echo title2025/title | sed -E s/[^]*([0-9])[^]*/\1/[^]*表示任意数量但不是的字符匹配范围被严格限定在第一对尖括号里正则立刻就对了。这个[^x]*代替.*的心法在处理HTML标签、JSON字段、日志字段时极其常用。一旦你掌握它很多贪婪匹配翻车现场都能轻松化解。
返回列表