news 2026/10/21 5:47:30

Linux文本处理三剑客精讲:grep、sed、awk实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux文本处理三剑客精讲:grep、sed、awk实战指南

从"Liunx"这个拼写说起。说实话,我第一次看到这个标题的时候也愣了一下,随即又觉得亲切——这不就是当年我们刚接触Linux时常见的状态吗?一边敲着命令一边搜着教程,连系统名字都还没拼利索。不过拼写归拼写,这个系列能一路写到第八篇,说明读者是真在跟着上手练。前面几篇我们先后折腾过虚拟机安装、目录结构与文件操作、用户权限、进程与服务、网络基础、磁盘管理,今天进入一个更贴近日常运维与自动化脚本的主题:文本处理三剑客。

为什么是文本处理?因为Linux世界里"一切皆文件"这句话,落到实际操作上,往往就是"一切皆是文本"。日志是文本,配置是文本,命令输出是文本,接口返回也是文本。无论你是做服务器运维、数据分析、程序开发还是写自动化脚本,每天都要跟文本打交道。而grep、sed、awk这三个工具,就是Linux下处理文本最重要的三件武器,也是从"会敲命令"走向"会写脚本"的分水岭。

这篇教程我准备拿一个贯穿全文的案例来讲:模拟一份Web服务器的访问日志,从里面筛选异常请求、提取关键字段、统计分布、生成格式化报表。跟着做完这一套,你就知道三剑客到底分别在干什么活,以及它们之间是怎么配合的。

1. 为什么文本处理在Linux学习里这么重要

1.1 日志和配置文件是绕不开的日常

不管你用Linux干什么,日志都是第一手情报来源。系统出故障了要看**/var/log/messages或者/var/log/syslog**,Web服务出问题了要看访问日志和错误日志,应用跑挂了要看业务日志。这些日志的体量往往非常惊人,一个中等流量的Web服务,一天的访问日志轻松上百万行。你不可能拿着编辑器从头翻到尾,唯一的办法就是用命令去筛选、提取、统计。

再说配置文件。Linux下几乎每个服务都靠文本配置,sshd_config、nginx.conf、my.cnf,改一处就要排查一处。这些文件动辄几百行,里面全是注释和可选配置,用眼睛找效率太低,用grep定位才是正经方式。

1.2 文本处理是自动化的地基

如果只是手动敲命令,crond定时任务、异常告警、日志归档、数据清洗这些自动化场景全都做不了。因为脚本的本质,就是"拿命令的输出当输入,再交给下一个命令处理",这就是管道符(|)的威力。而管道的两端,往往是文本处理器。

我见过不少学Linux的人,文件操作和权限管理学得挺熟,一到抓日志、写统计脚本就卡住,本质上就是因为文本处理这块没有建立起系统的认知。这篇的内容能补上这个短板。

1.3 我们这篇要处理的原始数据长什么样

为了让后面的操作不抽象,我先把贯穿全篇的示例文件准备好。这是一份模拟的Web服务访问日志,我给它起名access.log,大致长这样:

2025-11-01 08:12:33 192.168.1.101 GET /api/user/info 200 0.045 2025-11-01 08:12:35 10.0.0.23 POST /api/login 500 1.203 2025-11-01 08:12:37 192.168.1.101 GET /index.html 200 0.012 2025-11-01 08:12:40 172.16.8.9 GET /api/order/list 304 0.021 2025-11-01 08:12:42 10.0.0.23 POST /api/pay 500 2.145

每一行的时间、来源IP、请求方法、请求路径、状态码、响应耗时,都用空格分隔。这个格式是模仿主流Web服务的访问日志,实际上Nginx的默认格式会复杂很多,字段更多,但这里刻意简化,方便理解逻辑。后面你会发现,搞清楚每个字段是什么,比记住命令更重要。

2. grep:在一堆文本里快速捞针

2.1 最基本的使用方式

grep的任务很简单:在文件里搜索匹配的行,然后把它们打印出来。最基本的用法就是grep 关键词 文件名。

比如我想看日志里所有状态码为500的行,也就是出错的请求:

grep "500" access.log

输出会把所有包含"500"的行整行列出来。你会发现这里有个隐患:如果某个IP地址或者路径里也包含"500"这三个字符,也会被误匹配进来。这就是初学grep最容易踩的坑——grep匹配的是子串,不是单词。

想要精确匹配,办法是用正则表达式的边界符。后面会展开讲,这里先记住:

grep -w "500" access.log

-w表示只匹配整个单词,这样"500abc"这种就不会被捞出来。

2.2 常用选项一次讲清

在实际操作里,我90%的时间用的grep选项就这几个,我整理成了个速查表方便你记忆:

选项作用使用场景
-i忽略大小写匹配Error、ERROR、error时
-n显示行号在脚本或配置文件里找行号
-v反向匹配,取反过滤掉注释行、排除某个关键词
-c只统计匹配行数快速算日志里某种错误出现了多少次
-o只输出匹配的部分提取IP地址、URL等子串
-A 3附带匹配行之后3行看异常日志附近的上下文
-B 2附带匹配行之前2行看异常发生前的上下文
-C 2前后各2行最常用的上下文查看

举个例子,如果你排查一条报错,光看报错行本身往往看不出原因,得看它周围的上下文:

grep -n -C 2 "Traceback" app.log

这个命令会输出匹配行,以及它前2行和后2行,并且带上行号。排查程序崩溃时我几乎必用。

2.3 正则表达式:从普通搜索到模糊搜索

正则表达式是grep的核心,也是很多初学者觉得难的地方。其实不要被这个名字吓到,你可以把它理解成一种"带通配符的高级搜索方式"。它在普通关键词之外,加入了对"一类字符"和"数量"的描述。

举个例子,我要在日志中筛选出所有IP地址。一个IPv4地址是四段数字,每段1到3位,用点号连接。写成正则大约是:

grep -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log

这里[0-9]代表数字字符,+表示前面的字符出现一次或多次,\.是转义后的点号(因为点号在正则有特殊含义,必须转义才能匹配字面上的点)。

但是有个细节要注意:grep默认用的是基础正则表达式,它不认识+这个符号。你得用grep -E(相当于egrep)才能使用扩展正则语法:

grep -E -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log

我刚开始学的时候经常在这里栽跟头,同样是+,在主流的编辑器里能用,在grep里不加-E就报错或者匹配不到。这个习惯一定要养成:学会grep -E,少走一半弯路。

2.4 小技巧:只提取某一段文本

-o这个选项很多人忽视,但它在提取信息时特别好用。我们后面要统计IP,但日志里每行还有其他乱七八糟的内容,这时候只需要把IP部分捞出来:

grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log | head -10

我先说明一下这个正则:([0-9]{1,3}\.){3}表示"数字1到3位加一个点"这个组合重复三次,最后再跟一个1到3位的数字。head -10只是限制只看前10行,避免刷屏。

到这一步,你已经能灵活地从日志里筛出想要的行、抠出想要的字段了。但你会很快遇到一个新的问题:很多时候我不只想看,还想对文本做修改、删除、替换。这时候sed就该上场了。

3. sed:不打开编辑器也能改文件

3.1 理解sed的"流水线"工作方式

sed的全称是Stream Editor,流编辑器。它不像Vim那样打开文件让你交互式操作,而是以"流"的方式工作:读一行 -> 按你的指令处理 -> 输出/写入。你可以把它想象成一条流水线上站着一名工人,文件里的每一行经过他面前时,他按固定规则做动作——有的行打个标记(打印),有的行直接扔掉(删除),有的行改几个字(替换)。

正是因为这个"逐行处理"的模型,sed特别适合处理大文件。不管文件多大,内存里始终只装一行,稳得很。Vim打开一个上G的日志可能卡到怀疑人生,sed一秒钟跑完。

3.2 最常用的sed三板斧:打印p、删除d、替换s

先准备一个简单的测试文件test.txt:

apple 10 banana 5 orange 8 grape 12

打印所有行(默认sed就会输出,但显式加p参数能帮你理解处理过程):

sed -n 'p' test.txt

这里-n很关键,它表示"关闭默认输出"——因为如果既有默认输出又加p打印,每一行会出两次。我建议初学者带上-n去理解你写的是什么。

删除第2行:

sed '2d' test.txt

替换:把apple替换成pear:

sed 's/apple/pear/' test.txt

s命令是这个工具的绝对主角,它的格式固定为s/旧内容/新内容/标志。

3.3 替换操作的进阶玩法

基础替换只能改第一个匹配到的内容,想在每一行把所有的目标都换掉,要加g标志:

sed 's/a/A/g' test.txt

最实用的进阶技巧有两个。第一个是只处理匹配到某个关键词的行,比如只把包含orange的行里的8换成88:

sed '/orange/s/8/88/' test.txt

这个写法是先限定制行范围,再执行替换动作,日常改配置时用处极大。第二个是分组引用,把匹配到的内容"原样拿来"再加工。比如把apple 10改成10 apple,调换前后两段的位置:

sed 's/\([a-z]*\) \([0-9]*\)/\2 \1/' test.txt

\(...\)把匹配到的内容放进"组"里,后面用\1、\2按顺序引用。基础正则要用转义括号,用-E就能省去写反斜杠的麻烦。我强烈建议,凡是正则内容,一律用sed -E或grep -E,省心,也少出错:

sed -E 's/([a-z]+) ([0-9]+)/\2 \1/' test.txt

3.4 把改动写回文件的坑:-i参数

前面所有操作只是输出到屏幕上,并没有真正修改文件。想真正写回文件,用-i:

sed -i 's/apple/pear/' test.txt

注意这里有一个非常经典的大坑。Linux主流发行版默认带的是GNU sed,-i后可以直接接命令;但macOS自带的BSD sed,-i后面必须跟一个参数,哪怕不需要备份也要写成一个空字符串:

# GNU sed(Linux)写法 sed -i 's/apple/pear/' test.txt # BSD sed(macOS)写法 sed -i '' 's/apple/pear/' test.txt

我当年在一台mac上跑Linux的教程命令,结果报错extra characters at the end of d command,排查了半天才发现是-i的参数差异。如果你要在生产环境修改重要配置,我的习惯是每次都用带备份的方式:

sed -i.bak 's/old/new/' config.ini

这样会生成一个config.ini.bak备份文件。改坏了就一条命令还原,不至于把配置文件搞崩了手足无措。

4. awk:把文本当表格一样处理和统计

4.1 awk的核心认知:一行的字段拆解

如果说grep是"捞",sed是"改",那awk就是"算"。awk最擅长的,是把每一行按分隔符拆分成若干字段,然后逐行处理、统计、汇总。你可以把它想象成一个Excel透视表处理员,原文每一行就是一条记录,每个字段就是一列。

默认情况下,awk用空白(空格或Tab)作为分隔符。对我前面的access.log来说,每一行天然就被分成了7个字段。awk用$1表示第一个字段,$2表示第二个字段,以此类推,$0表示整行。

先来看最简单的操作:打印第1列(日期)和第7列(响应耗时):

awk '{print $1, $7}' access.log

这时你可能会问:怎么和普通文本命令的输出不太一样?注意awk的语法结构是"条件 {动作}"结构——前面是条件,后面花括号里是动作,不写条件就对所有行执行动作。越早习惯这个模型,你学awk越顺畅。

4.2 用- F指定分隔符

真实世界的日志不少是用逗号或者制表符、竖线分隔的,这时候-F参数就派上用场了。我模拟一份CSV风格的数据data.csv:

2025-11-01,192.168.1.101,GET,200 2025-11-02,10.0.0.23,POST,500

按逗号拆列,打印第2列IP:

awk -F ',' '{print $2}' data.csv

这个技巧在处理导出数据、CSV报表时非常常用。

4.3 内置变量NR与NF

awk自带一批内置变量,其中NR和NF是最常用的两个。NR是当前处理到第几行,NF是当前这一行有多少个字段。

打印每一行的行号和字段数量:

awk '{print NR, NF}' access.log

打印最后一列,不管这一行有多少列,都能准确定位:

awk '{print $NF}' access.log

这个"$NF表示最后一列"的写法特别实用,因为日志或者表格的格式经常变,但你永远关心的是"最后一项"。我写统计脚本时,只要想取响应时间或状态码这类尾字段,第一反应就是用$NF。

4.4 按条件过滤行

awk真正的威力在条件过滤。比如,我只想看状态码是500的错误请求的完整行:

awk '$6 == 500 {print $0}' access.log

更实用一点:筛选出响应耗时超过1秒的慢请求,也就是第7列数值大于1的行:

awk '$7 > 1 {print $0}' access.log

初学awk时要把==和=死死分清楚:==是比较,=是赋值。我见过不止一个人写条件过滤时用了单等号,结果awk没报错,但输出结果完全不对,因为它在做赋值操作而不是条件判断。

4.5 统计功能:给日志算一笔账

awk的统计能力是它比grep强出一大截的原因。它可以在处理所有行之前做初始化(BEGIN),处理完所有行之后做输出(END)。

统计状态码500出现了多少次:

awk '$6 == 500 {count++} END {print "500次数:", count}' access.log

统计所有请求的平均响应耗时:

awk '{sum += $7} END {print "平均耗时:", sum/NR}' access.log

这里sum是一个自定义变量,不需要提前声明,直接用就行。NR在处理完最后一行时会等于总行数,所以sum/NR就是平均值。用这套思路还能做很多事:统计某个IP的请求总数、计算某个接口的耗时总和、按小时统计请求量,全部是"累积 + 汇总"的路子。

4.6 更高级的玩法:按键值做统计

如果只做全量统计,距离"透析日志"还差一步。实际中最常见的需求是分组统计:比如统计每个IP访问了多少次。这在awk里可以用一个"数组"实现:

awk '{ip[$2]++} END {for (i in ip) print i, ip[i]}' access.log

稍微解释一下:ip[$2]++的意思是,以第2列(IP)作为下标,每遇到一个相同的IP,计数器就加1。等所有行处理完,用for循环把键和值都打印出来。你会发现一个刚装好的Linux发行版就是标准的awk,不需要额外装任何软件,就能完成很多需要用编程语言才能做的统计工作。

5. 三剑客协同:从日志到报表的完整实战

5.1 实战场景设定

在真实环境里,三把武器很少单兵作战。我们来做一个完整的小任务:从访问日志中统计所有状态码为500的请求,按IP统计出现次数,找出"出错大户",最后输出一个排序后的报表。

5.2 一步步拆解管道命令

第一步,先用grep筛出状态码为500的行。注意这里不能用太简单的grep "500",因为路径里可能带500。我们结合状态码在行尾这个特征:

grep -E " 500$" access.log

$是正则里的"结尾锚定",这样只有行尾是空格加500的才算数。这比用awk更简单直接,没问题,但在这篇里为了展示分工,我统一用awk来过滤。

第二步,从这些行里提取IP。我们前面已经知道了:awk里$2就是IP:

awk '$6 == 500 {print $2}' access.log

第三步,在awk里直接做统计并打印:

awk '$6 == 500 {count[$2]++} END {for (ip in count) print ip, count[ip]}' access.log

第四步,按次数从大到小排序。awk输出是乱序的,需要用sort配合。sort -rn里的-r是反转(从大到小),-n是按数值排序(不然10会排在2前面,因为按字典序"10"小于"2"):

awk '$6 == 500 {count[$2]++} END {for (ip in count) print ip, count[ip]}' access.log | sort -rn -k 2

第五步,加上一个标题行,顺便生成一份可读性更好的报表。这里用sed来拼接/插入一行,也可以用echo然后重定向。

我把完整的一条命令写成这样的形式:

echo "IP 错误次数" echo "---------------------" awk '$6 == 500 {count[$2]++} END {for (ip in count) print ip, count[ip]}' access.log | sort -rn -k 2

5.3 再增加一个需求:统计接口的平均耗时

实际运维中,最常见的任务是"找出哪些接口变慢了"。我们扩展一下:统计每个请求路径(第4列)的平均响应耗时(第7列),只关心出现次数超过一定阈值的接口。

这里要用到awk的两个数组,一个累计耗时,一个累计次数:

awk '{sum[$4] += $7; cnt[$4]++} END {for (path in sum) if (cnt[path] > 3) print path, sum[path]/cnt[path]}' access.log | sort -k 2 -rn

输出结果会是一个按平均耗时排序的接口列表,是不是已经有点"性能分析报告"的意思了?

5.4 把报表保存成文件

在处理完结果后,可以用重定向把报表保存下来:

awk '$6 == 500 {count[$2]++} END {for (ip in count) print ip, count[ip]}' access.log | sort -rn -k 2 > error_report.txt

保存下来的文件就能用来发邮件、写值班日报、或者作为脚本的输入继续处理。整个链路就是:grep/awk负责筛选和统计,sed负责改写文本,sort负责排序,重定向负责落盘。每个命令各干一摊,用管道串起来,这就是Linux命令行最舒服的协作方式。

6. 实战中的常见坑与我的排查心得

6.1 匹配不到内容,先怀疑隐藏字符

grep搜不到结果,很多人的第一反应是"关键字写错了"。但更常见的原因是文件里的不可见字符在捣乱。比如Windows下编辑的文件,每行末尾有一个\r回车符,Linux下的命令看不到但实际存在。这种文件你用grep "pattern"匹配,通常会失败,因为行尾多了个\r。

排查方法很简单,用cat -A看一下文件真实内容:

cat -A access.log | head -5

如果看到行尾有^M$,说明这就是Windows换行符。用sed清洗一下:

sed -i 's/\r$//' access.log

这个命令能把行尾的\r删掉。我在处理从别处拷贝过来的脚本、配置文件时,遇到各种诡异问题,十有八九是换行符和编码。

6.2 正则的"贪婪"特性:.* 匹配到的是最后面

正则里的.*表示"任意字符出现任意多次",它在默认情况下是贪婪的,会尽量匹配最多的内容。举个例子,从一行文本里提取第一个引号之间的内容:

name="alice" age="20"

你想提取alice,写".*"会把"alice" age="20"整个匹配出来,因为它从第一个引号开始,一直吃到最后一个引号才算完。这就是经典的正则"贪婪匹配坑"。

解决办法是用"[^"]*"——意思是匹配除了引号以外的任意字符:

echo 'name="alice" age="20"' | grep -o '"[^"]*"'

这个细节在解析JSON行、HTML标签、日志参数时经常用到。第一次遇到时可能会困惑半天,理解了"贪婪"机制后就通了。

6.3 别什么都用管道,先拆开单步验证

我知道很多人(包括从前的我)学完三剑客,容易陷入"一条命令特别长"的炫技模式。把grep、sed、awk、sort、uniq全串在一行里,写的时候很爽,出错的时候很惨——你不知道到底哪一步出了问题。

我的习惯是,先单步执行,验证每一步的输出,确认无误后再拼接起来。写一个管道命令绝不直接一次成型,而是像这样一步步来:

# 第一步:先看原始数据有没有过滤对 awk '$6 == 500 {print $2}' access.log # 第二步:加上统计,先看结果长什么样 awk '$6 == 500 {count[$2]++} END {for (ip in count) print ip, count[ip]}' access.log # 第三步:再排序 ... | sort -rn -k 2 # 最后:再加上表头和重定向

这样做的好处很明显:一旦某一步输出不符合预期,你立刻知道问题出在这一步,而不需要拆掉整条命令去猜。这个排查思路适用于所有命令行组合的调试,也是我建议你在初期就养成的工作习惯。

6.4 定期用三剑客"复盘"自己的命令历史

最后补充一个我屡试不爽的学习方法:在系统上敲的命令都会记录在用户主目录下的历史文件里(比如~/.bash_history),你每周可以花十分钟去翻一翻,看看自己高频使用哪些命令、哪些操作还能被命令替代/简化。比如你发现经常在日志里搜同一个关键字,就应该考虑写一个简短的shell函数去封装它。

其实文本处理就是这样,看着命令不少,但真正高频的也就那么几十个。多用几次,多踩几次坑,它们就会变成肌肉记忆。这篇的内容虽然按"grep、sed、awk"分开讲,但实际干活时三件套是永远连在一起用的,你用熟了之后,自然会形成一种手感:看到一段文本,脑子里已经自动在拆解"该用什么命令去切它、筛它、算它"。

下一篇我打算接着这个基础,讲Shell脚本的控制结构——条件判断和循环,正好把这篇的文本处理能力编排成可复用的脚本。如果你在跟练时有什么有趣的坑,欢迎在评论区聊聊,互相长点经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 5:46:56

提示词方案性能分析指南:从评测集到量化指标的完整流程

很多做提示工程的同学,都会有这样一个困惑:我的提示词方案明明在几个测试例子上表现不错,可一旦放到真实场景里,效果就忽上忽下,甚至直接翻车。问题到底出在哪?是模型不行,还是提示词不够好&…

作者头像 李华
网站建设 2026/10/21 5:46:48

LangChain对接本地大模型:私有化部署环境配置完整指南

前阵子有个朋友找我,说他在企业内部做私有化知识库问答,模型是部署在内网服务器上的,但代码层面一直卡在第一步——LangChain开发环境怎么搭都不顺。不是装不上,就是装上了调用本地模型报错,再不然就是各种版本冲突。折…

作者头像 李华
网站建设 2026/10/21 5:44:23

Flutter本地HTML/JS集成实战:高性能双向通信与工程化方案

1. 为什么Flutter里非要“调用HTML”?——不是为了炫技,而是解决真实场景的刚性需求在某跨平台系统开发中,我接手了一个已上线半年的Flutter应用,核心功能是展示动态生成的营销活动页。这些页面由运营后台实时配置,内容…

作者头像 李华
网站建设 2026/10/21 5:44:04

从模糊词根到可执行方案:rea 读取与响应式入口的工程实现

1. 从“rea”这个模糊词根说起:它到底指向什么第一次看到“rea”这个标题,我其实也愣了一下。没有正文、没有关键词、没有摘要,只有一个三字母的词根。这种极简输入在真实工作里并不少见——同事甩过来一个命名、一个缩写、一个半截单词&…

作者头像 李华
网站建设 2026/10/21 5:43:49

机器人导航地图模块工程化:从坐标变换到代价地图的完整实践指南

lingbot-map 这个名字,最早是写在某个内部仓库 README 里的一句“地图与导航定位模块”。它解决的,其实就是机器人最绕不开的三个问题:我在哪、周围什么样、要怎么过去。地图模块不是整个机器人里最抢眼球的部分,但它是所有后续决…

作者头像 李华
网站建设 2026/10/21 5:43:17

JDBC防SQL注入:PreparedStatement预编译与参数化查询实战

做后端开发的这些年,我见过太多因为SQL注入被打穿的教训。有一次某传统企业上线一套报表系统,查询接口的参数被直接拼进SQL字符串里,攻击者用两个fragment就拖走了整张用户表。还有一套内部管理系统,登录页被人用万能密码直接绕过…

作者头像 李华