资讯动态

Linux正则表达式入门:从grep、sed到awk的实战指南

发布时间:2026/10/16 4:53:48 来源:尧图企业网站定制
刚接触Linux命令行的时候我最头疼的其实不是那一堆命令参数而是这些命令里动不动就出现的正则表达式。它看起来像一堆乱码却又反复出现在grep、sed、awk甚至是vim里绕不开也躲不掉。后来真正花时间把正则的基础逻辑捋清楚之后才发现这东西一旦通了处理文本的效率会直接上一个台阶很多原本要写几十行脚本才能搞定的活儿一行命令就结束了。这篇内容就是写给正准备迈过这道坎的朋友。我不会堆砌复杂的语法规则而是会从正则究竟是“干什么用的”说起把最常用、最好使的那部分符号逐个拆开讲透再放到grep、sed、awk这些真实场景里跑一遍最后聊一聊我实际踩过的坑。这套东西适合零基础的小白跟着一步步操作也适合已经会用一点、但总觉得自己理解不透的人查漏补缺。1. 正则到底是什么它和文件名通配符有什么区别我见过不少人一开始就把正则和Linux里另一个“通配”机制搞混结果写出来的匹配规则怎么看怎么别扭。所以在讲具体符号之前非常有必要先把这两个概念掰扯清楚。1.1 用生活类比理解“模式匹配”你可以把正则表达式理解为“一类文本的配方”而不是某一段具体的文字。比如你想在一堆简历里筛出所有手机号你关心的不是某一个电话号码而是“以1开头、第二位是3到9、后面跟着9位数字”这种特征这个特征就是一条正则表达式。它描述的是结构而不是内容本身。在Linux环境下正则平时用得最多的地方是文本过滤和批量处理。它的典型工作流程是先拿正则去“描述”你要找的内容长什么样再交给工具命令去逐行扫描把符合描述的那些行或者片段挑出来。而文件名通配符*、?、[]是另一套东西它是Shell在解析命令时用来匹配文件名的。比如ls *.log里的*表示“任意长度的任意字符”配合的是文件系统里的文件名。虽然写法上有相似之处但它们的语义完全不同。1.2 计算思维里的“描述逻辑”正则表达式的核心价值是提供了一种极其精简的方式来描述“重复”和“选择”。没有它的时候你想匹配“abc、abbc、abbbc”这几个不断延长的字符串得把它们一个个列出来有了正则你只需要写ab*c其中b*代表“0个或多个b”一条规则就覆盖了所有变化。这种描述能力背后是形式语言理论中的正则语言概念虽然不必深入研究理论但理解这一点很有帮助正则表达式能描述的模式是有边界的它不适合处理“需要递归计数”的问题比如匹配“成对出现的括号”那类任务应该交给脚本语言的完整语法分析能力硬用正则反而会陷入误区。我经常和身边同事说正则不是万能的但它覆盖了文本处理中百分之八九十的日常需求。你只需要把最核心的那十几个符号用熟就能解决绝大多数的查找、统计、替换问题。2. 基础元字符逐个拆解配合举例一次看懂这一节是全文的基石。我不按文档式的方法罗列字符而是从“你要描述什么样的文本特征”这个角度出发告诉大家什么时候该用哪个符号为什么用它能行。2.1 字符匹配类精确匹配与任意占位最直观的正则符号就是普通字符。你在正则里写abc它就严格匹配abc这串字符一个不多一个不少。这是所有规则的地基其他所有符号都是在这个“直接匹配”的基础上进行抽象和扩展。真正拉开差距的第一个符号是.它匹配“任意单个字符”。注意这里是“一个”不是“一串”。如果你写a.c它匹配的是abc、a1c、a-c但不能匹配ac因为那儿缺了一个字符也不能匹配abbc因为中间多了字符。打个比方.就像是模板里的一个空槽位任何单个字符都能填进去但必须有东西占着这个位置。在基础正则中还有一些针对位置和边界进行控制的符号用得非常多。^用来锚定行首$用来锚定行尾。比如^root只匹配那些“以root开头”的行而root$只匹配“以root结尾”的行。这两个符号的价值在于精准定位避免在一长段内容里误伤无关部分。2.2 字符集合与排除逻辑如果你要匹配的不只是“任意字符”而是限定“这几个字符里的某一个”就要用到字符集合[]。这是一对真正意义上的“按需定制”符号。[abc]匹配“a、b、c三个字符中的任意一个”而[a-z]匹配“任意一个小写英文字母”[0-9]匹配“任意一位数字”。字符集合还支持一个极其常用的操作排除。在方括号内部开头加上^语义就从“匹配集合里的字符”变成“匹配不在集合里的任意字符”。比如[^0-9]匹配“任意一个非数字字符”。我特别想强调一个细节正则表达式里[]内部的很多符号会失去原本的“魔法”。例如在括号外面.代表任意字符但在[.]里面它就只是一个普通的句号。这一点在匹配IP地址、版本号这类带有大量点号的文本时非常关键。2.3 重复与量词从通配到灵活计数如果说上面那些符号解决了“匹配什么”重复类元字符则解决了“匹配多少个”。最基础的是星号*它表示“星号前面的那个字符可以出现0次或任意多次”。注意“0次”意味着即使那个字符不出现整个模式也算匹配成功。比如ab*c匹配ac、abc、abbc甚至abbbbbbbc。紧跟着的加号在基础正则BRE里不是标准支持的在扩展正则ERE里它表示“前面的字符至少出现1次”。abc能匹配abc和abbc但不能匹配ac。问号?则表示“前面的字符出现0次或1次最多一次”ab?c只匹配ac和abc中间不能有更多b。扩展正则还支持用花括号指定重复次数。{n}表示恰好出现n次{n,}表示至少出现n次{n,m}表示出现n到m次之间。比如[0-9]{3}匹配连续的三位数字在匹配电话号码区号、年份、端口号这类定长内容时非常好用。使用这些量词时最容易犯的直觉性错误是混淆“匹配的优先级”。正则里量词只约束它前面的那一个字符或单元不会无限向左右延伸。如果你写了ab*它是指“a后面跟着0个或多个b”而不是“ab这个整体重复任意次”。要想对一整个片段施加重复必须用后续会讲到的括号分组把那个片段先“圈起来”。2.4 转义与特殊字符陷阱正则里有一批原本有特殊含义的符号当你想匹配它们本身的字面意思时直接写是不行的必须加上反斜杠\进行转义。最典型的包括点号\.、星号\*、问号\?、方括号\[和\]、反斜杠本身\\等。举个例子你想在文本里找出所有“以句号结尾”的行写成$似乎理所当然但问题是$在正则有“行尾”的含义真正要匹配句号本身正确写法是\.$。我第一次写这个表达式时就忘了转义结果匹配结果乱七八糟排查了很久才意识到问题出在哪。转义是最容易犯的一类低级错误但它也确实最值得花时间记牢。建议你在写任何正则之前先扫一眼待匹配文本里有没有这些特殊字符提前把它们转义好能省下大把调试时间。3. 基础正则还是扩展正则区别和选型思路写完上面的符号你可能已经发现我一会儿提“基础正则”一会儿提“扩展正则”这两者到底有什么不同实际用在Linux命令里又该怎么选3.1 两类正则在命令中的默认形态Linux系统里普遍存在两套正则语法体系基础正则BRE和扩展正则ERE。它们的字符集和转义规则不完全一样同一个表达式可能因为语法标准不同而产生完全不同的匹配结果。在grep命令中默认使用的是基础正则。基础正则里、?、{}、|这几个符号都还“只是普通字符”不表示量词或选择。让它们恢复特殊含义需要加上反斜杠比如\、\?、\{2\}、\|。这写起来相当别扭。如果你用grep -E或者直接使用egrep就切换到了扩展正则。在扩展正则里、?、{}、|不再需要额外转义写起来直观得多。sed命令的情况类似默认用基础正则加上-E选项后可启用扩展正则。awk则比较特殊它自身就默认使用扩展正则。初学者有一个常见的困惑同一个正则在grep abcdef里匹配不到东西在grep -E abcdef里却可以因为的语义完全不同。这不是命令出错而是语法标准不同导致的。3.2 分组与选择括号的真正力量无论是基础正则还是扩展正则圆括号()都是实现“分组”的关键。分组让短语可以用量词修饰也方便后续反向引用。(ab)表示“ab这个整体出现一次或多次”能匹配ab、abab、ababab这是单个字符的量词做不到的。分组还有一项重要价值就是配合竖线|实现“多选一”。cat|dog在扩展正则里表示“匹配cat或者dog”它其实等价于“分组括号内做选择”(cat|dog)。之所以要加括号是因为|的优先级极低如果你写cat|dog food它会被理解为“匹配cat或匹配dog food”而不是“匹配cat或dog后面跟着food”。这一点在实际书写时特别容易踩坑。基础正则里的\|和\(、\)同样需要转义才具备这种能力。我个人是这么记住的在不需要刻意兼容老脚本的前提下能用扩展正则就用扩展正则省心得多。4. 在Linux命令里练手grep、sed、awk与vim实例纯粹背符号没有意义正则的生命力全在实战里。我选四个高频场景带大家过一遍实际用法每一段都给出可以直接拿走的示例命令。4.1 grep过滤日志与查找关键内容的利器grep是我的日常主力。它干的事很简单读入文本流把匹配正则的行挑出来。假设你有一份访问日志access.log想找出所有状态码为500的行可以写成grep -E HTTP/1\.[01] 500 access.log这里HTTP/1\.[01]匹配HTTP/1.0或HTTP/1.1因为点号是任意字符必须用\.转义[01]表示0或1。这种精确写法可以避免误匹配过宽的日志行。再看一个更实用的例子查找所有“IP地址以192.168开头”的日志行。grep -E ^192\.168\. access.log^锚定行首192\.168\.精确匹配前两段IP。若不加^日志正文中的其他位置也可能出现这样的串导致命中范围被扩大。配合-o选项grep还能只输出匹配到的那一小部分而不是整行。这在提取数据片段时非常有用比如提取所有IP地址grep -oE [0-9]{1,3}(\.[0-9]{1,3}){3} access.log这个表达式值得拆解一下[0-9]{1,3}匹配1到3位数字(\.[0-9]{1,3}){3}表示“点号加1到3位数字”这个整体重复三次合在一起就是完整的四段式IP。虽然它没法识别像999这种非法段位但在绝大多数日志分析场景里已经足够用了。4.2 sed用正则做“查找替换”和“行筛选”sed最出圈的功能之一便是替换。它的基本结构是sed s/目标正则/替换内容/选项。比如把文件里所有的“foo”换成“bar”sed s/foo/bar/g file.txt这里尾部的g表示“全局替换”不加的话每一行只会替换第一个匹配到的目标串。替换内容里代表“整个匹配到的文本”在处理“在原串前后加内容”的场景下非常省力。比如把每个数字串两侧包上中括号sed -E s/[0-9]/[]/g file.txt在替换段里会自动变成实际匹配到的数字所以[]就等于把原数字用方括号包了起来。注意这个用法只对sed和部分其他工具有效换成编程语言的替换函数时符号可能不同需要留意。sed还能用行地址来约束“哪些行执行正则操作”比如只处理前三行sed 1,3s/foo/bar/g file.txt以及删除匹配正则的行sed /^#/d config.conf这条命令会把所有以#开头的注释行删掉在清理配置文件时非常常用。4.3 awk字段级处理的进阶姿势如果grep和sed是针对“行”的瑞士军刀那awk就是把“行”进一步切成“字段”来处理的工具。它默认按空白字符空格、Tab等把每一行切开$1代表第一个字段$2代表第二个以此类推。一个经典场景从登录日志中提取登录成功的用户名列表。awk /accepted/{print $1} /var/log/auth.log在awk里/正则/用来筛选当前行是否满足模式{print $1}则是找到匹配行后要执行的动作。这条命令的含义是只要某行包含accepted就取出该行的第一个字段打印。实际字段位置取决于日志格式需要先观察再微调。awk的正则还能作用于特定字段。比如打印/etc/passwd中所有“登录Shell为/bin/bash”的用户名awk -F: $7 ~ /\/bin\/bash/ {print $1} /etc/passwd-F:告诉awk用冒号做分隔符$7是第七个字段即登录Shell~是“匹配”操作符/\/bin\/bash/是正则。注意这里正则里的两个正斜杠都需要用\/转义因为正斜杠本身是awk正则的定界符。这个反斜杠确实让表达式变得难读通常我会改用别的定界方式或者提前用变量过渡避免现场调试时看花眼。4.4 vim编辑器里的正则魔法vim的命令行模式同样支持正则这是很多人整理文档时不太注意、但用起来极爽的功能。比如把全文里的多个连续空行替换成单空行:%s/\n\{2,}/\r\r/g在这里\n是换行符\{2,}表示至少连续两个换行\r用于插入一个新的换行。vim用的语法同样是基础正则所以量词的花括号需要转义这与grep默认模式一致。再比如给所有行首加上-快速做出一个Markdown尚可阅读的清单:%s/^/- /理解“编辑器里正则生效的位置”很重要%表示作用于所有行s/.../.../是替换指令正则写在第一个正斜杠和第二个正斜杠之间。这一套熟悉了之后整理批量性文本的效率非常可观。5. 常见错误、调试方法与我踩过的坑正则在刚上手时确实给人“反馈很玄学”的感觉明明看起来没写错结果却不如人意。下面这些是我自己反复踩过、也看到身边人反复踩的坑讲出来希望大家可以直接避开。5.1 字符转义与优先级造成的乌龙我最初写正则时犯过最多次的错就是“漏转义”。曾有一次想匹配文本里的“1.1”随手写了1.1结果匹配出来一堆111、141、1a1因为.匹配了任意字符。后来学乖了凡是要匹配字面点号的一律写\.再也没出过这种问题。另一类是优先级错误。想匹配“abc或def开头的行”如果写成^abc|def实际含义是“以abc开头的那部分或者def这个单词”而不是“以abc开头或以def开头”。正确写法应该是^(abc|def)。竖线|的优先级很低低到它几乎会把整个表达式切成左右两半需要分组来约束它的作用范围。还有一类容易忽略的细节是“字符集合内部的排除符”。[^a-z]表示“不是小写字母的字符”而[a-z^]则表示“小写字母或^这个字符”。^在方括号内的位置决定了它的含义写反了就是完全不同的规则。5.2 写好正则后的现场调试技巧正则表达式属于那种“不跑起来就不放心”的东西所以调试手段一定要顺手。我平时最常用的是grep --color让匹配结果直接高亮显示一眼就能看到命中的范围。grep --color -E 你的正则 测试文件如果是临时测试而手上没有合适的测试文件可以直接用管道把一段文字喂给grepecho abc123 | grep -oE [a-z][0-9]对于特别复杂的正则python3 -c配合re模块也是我的好帮手可以更细致地输出匹配对象的具体位置。调试时我通常会把“期望匹配的样例”和“期望排除的反样例”同时摆出来测试确保正则既不会漏掉该匹配的也不会误伤不该匹配的。调试正则的一个核心心智模型先把目标文本划成三块看——“开头部分”“中间主体”“结尾部分”然后逐块设计匹配规则再回来拼装。别试图一口气写出一整条完美正则拆开来验证整体自然就对了。5.3 贪婪与懒惰匹配的实际影响在扩展正则和大部分编程语言的正则引擎中默认的重复匹配是“贪婪”的能多匹配就多匹配。比如你在a.*b中匹配字符串a123b456b它会匹配到最后一个b整段a123b456b都被命中。如果不想要这么贪希望匹配到第一个b就停需要写成a.*?b其中?跟在量词后面表示“懒惰匹配”。Linux命令行中grep默认的基础正则和扩展正则并不支持懒惰量词的写法这一点让很多从其他语言转过来的朋友困惑。遇到“只想匹配到第一个边界”的需求时我的建议是别硬抠一条正则改用两步操作先用宽松规则定位区间起点再用具体字符或多次处理截出想要的部分。复杂的单条规则写出来很难维护拆开处理反而清晰可控。6. 正则综合实战一个可复用的日志分析思路掌握了基础符号和常见命令我们用一个足够现实的例子把所有东西串起来给定一份服务运行日志统计每种错误级别INFO、WARN、ERROR出现的次数并列出所有出现ERROR的行。假设日志格式长这样2025-01-10 08:12:33 INFO 模块A启动成功 2025-01-10 08:12:41 WARN 模块A响应时间超过阈值 2025-01-10 08:15:02 ERROR 模块B连接失败首先统计三个级别的出现次数用grep与sort、uniq配合grep -oE \b(INFO|WARN|ERROR)\b app.log | sort | uniq -c这个表达式中\b表示词边界作用是防止ERROR去匹配到ERRORS这种更长词的局部。(INFO|WARN|ERROR)分组三选一-o只提取匹配到的部分再通过管道把结果交给sort排序、uniq -c统计次数。这种三段式管道是我处理频率统计问题时的默认动作。接着提取所有ERROR行的完整内容grep -E ERROR app.log注意这里的ERROR前后都留了空格能有效防止它的某个子串恰好出现在别的单词里。如果日志里字段位置固定也可以交给awk精准截取比如只打印ERROR行的前两个字段时间和级别awk / ERROR /{print $1, $2} app.log这样的组合方式看着很简单但就是这些基础命令的排列组合支撑起了我在没有专业日志平台时的所有快速分析需求。遇到一个临时要排查、又不想上重型工具的问题时这套“正则管道”的打法永远是最快出结果的一条路。学习正则不要去背所有的符号和用法。你只需要把最常用的十几个字符练熟在grep、sed、awk、vim里各跑通几个典型场景之后遇到更复杂的写法都有能力通过手册和调试工具现场搞定了。正则真正难的不是语法而是“把模糊需求转化成精确模式”的思维方式这个思维能力只能靠一次次的实战去磨。我个人在写了很多脚本以后发现掌握正则给我带来的最大改变不是“命令行用得花哨”而是面对文本问题时不再发怵——反正无非就是把模式描述清楚剩下的交给工具。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑