资讯动态

Linux正则表达式实战:用grep、sed、awk高效处理文本

发布时间:2026/10/15 8:48:37 来源:尧图企业网站定制
Linux 命令行里有一种能力很多初学者一开始完全没当回事等到真正需要处理日志、批量改配置、写自动化脚本的时候才后悔没有早点学它就是正则表达式。说实话正则不是那种需要背完整本语法书才能上手的东西核心规则就那么几条配合 grep、sed、awk 这三个命令能解决的问题多到超出想象——从几千行日志里捞出所有超时请求把几十个配置文件里的旧域名批量替换成新域名或者从命令输出里抽出指定字段全靠它。这篇是【Linux 基础入门】系列的第十二篇我按照自己当年学习时的认知顺序来讲先搞清楚为什么绕不开它再把最常用的语法过一遍然后落到 grep、sed、awk 三个命令上最后说说新手最容易踩的坑。如果你一直知道有正则这回事却始终没有真正用起来这篇应该能帮你把这块拼图补上。1. 学了这么久的 Linux为什么一定要补上正则这块拼图1.1 你迟早会碰到的三个高频场景先说三个我见过无数次的真实场景。场景一线上服务报错日志文件一天生成几百 MB你想把里面所有状态码为 5xx 的请求行捞出来。没有正则你只能先 grep 500再 grep 502再 grep 503遇到 504、505 还得继续一个个加。有正则一条命令就能覆盖整个 5xx 范围还能顺手把耗时超标的请求也一起筛出来。场景二接手一台服务器发现某个服务的配置文件里写的是旧的内网域名几十个文件要批量替换成新域名。手工一个个改不现实sed 配合正则在几秒内完成还能先预览确认再真正写回文件。场景三写监控脚本时需要从一段命令输出里提取 IP、时间或数值。awk 配合正则按字段匹配脚本就能自动从杂乱文本里抽出结构化信息。这三个场景的共同点是你要在文本里找的不是某个固定字符串而是符合某种形状的字符串。正则就是描述这种形状的语言。你当然可以靠一串 grep 加管道硬凑但正则学明白之后你会发现以前那些笨办法不仅是慢而且容易漏。漏一条错误日志可能比慢几分钟严重得多。1.2 正则的三种流派先搞清楚再动手否则全是乱象Linux 下最常见的正则实现有三种BRE基本正则、ERE扩展正则、PCREPerl 兼容正则。很多人学正则觉得混乱其实混乱的根源不是语法本身而是没先分清当前命令到底用的是哪种流派。BREgrep 默认使用。特点是( )、{ }、、?这些符号默认当作普通字符想用特殊功能必须加反斜杠比如\( \)表示分组\{3\}表示重复三次。EREgrep -E / egrep、sed -E、awk 默认使用的形式。( )、{ }、、?本身就是特殊符号不用加反斜杠写起来清爽很多。PCREgrep -P 以及大多数编程语言里用的风格支持\d、\w、\s、\b、懒惰量词这些更现代的特性。三种流派对同一个功能的不同写法我列个表对照一下想表达的功能BRE 写法ERE 写法PCRE 写法分组\(abc\)(abc)(abc)重复 3 次a\{3\}a{3}a{3}一个或多个a\aa匹配数字[[:digit:]][[:digit:]]\d新手最常见的困惑——我明明写了(abc)为什么匹配不到任何东西——十有八九是因为你在默认 grepBRE里用了 ERE 的写法。所以拿到一个正则先问自己当前命令用的是什么流派。这也是我后面一直建议的原因只要表达式稍微复杂一点就统一加-E别在脑子里反复切换转义规则。2. 正则表达式的基本语法从单个字符到整个结构2.1 字面量与字符组先学会匹配可列举的字符正则最基础的单元是字面量比如abc匹配文本中任何包含 abc 的连续片段这个简单不用多说。真正开始有威力的是字符组。[abc]表示匹配 a、b、c 中的任意一个字符[a-z]表示匹配小写字母[0-9]表示匹配数字[^0-9]表示匹配任意一个非数字字符。注意字符组匹配的是一个字符不是一串字符。你想匹配数字或字母就写[0-9a-zA-Z]想匹配任意一个中文标点就得把标点列全。在 Linux 的 POSIX 正则里还有一组预定义字符类写法是[[:xxx:]]。常用的有[[:digit:]]数字、[[:alpha:]]字母、[[:alnum:]]字母数字、[[:space:]]空白字符、[[:upper:]]大写字母、[[:lower:]]小写字母。如果你用 grep -P可以用\d、\w、\s这种更短的写法但在默认 grep 里\d是无效的这一点留到坑的部分细说。举个实际例子从/etc/passwd里找出用户名的第一个字符是大写字母的行可以写grep ^[[:upper:]] /etc/passwd2.2 位置锚点不占字符的隐形规则^匹配行首$匹配行尾。这和编程语言里匹配字符串开头的概念不完全一样在 grep 这类按行处理的命令里^和$默认指的就是每一行的开头和结尾。几个典型用法grep ^root /etc/passwd # 找以 root 开头的行 grep bash$ /etc/passwd # 找以 bash 结尾的行 grep ^$ file # 找空行另一个容易被忽略的锚点是单词边界。GNU grep 支持\和\表示单词边界比如grep \cat\ file只匹配独立的单词 cat不会误匹配 catalogue、catfish 这类包含 cat 的长单词。PCRE 里对应的是\b。这个功能在做英文文本处理时非常实用比如统计某个单词真正出现的次数。2.3 量词控制重复次数别让贪婪成为默认正则的重复规则是绕不开的重点*表示前面的字符出现 0 次或多次表示前面的字符出现 1 次或多次?表示前面的字符出现 0 次或 1 次{n}恰好 n 次{n,}至少 n 次{n,m}n 到 m 次举例来说匹配 IP 地址的一段可以用[0-9]{1,3}匹配连续两个及以上数字可以用[0-9]{2,}。量词前面必须有一个对象这个对象可以是单个字符、字符组也可以是后面要讲的分组。这里有一个特别反直觉的点默认情况下量词是贪婪的也就是它会尽量多匹配。比如文本是a111b222b正则a.*b会从第一个 a 一直匹配到最后一个 b结果是a111b222b而不是你直觉里的a111b。这个特性在提取数据时经常造成我明明写了.*为什么吞掉这么多的困惑。在 PCRE 里可以用.*?改成懒惰匹配让它尽量少匹配grep -P 支持这种写法。2.4 分组与引用把匹配结果存起来用括号把一部分正则包起来就是分组。分组有两个作用一是把一组字符当作整体应用量词比如(ab){2}匹配 abab二是把匹配到的内容捕获下来后面用\1、\2引用。sed 里最典型的例子echo hello world | sed -E s/(hello) (world)/\2 \1/ # 输出world hellogrep -E 里也可以引用捕获的内容。比如找出挨着重复出现的英文单词在 GNU grep 下可以这样写grep -nE (\[a-zA-Z]\) \1 file它的含义是匹配一个单词、空格、然后又是同一个单词。很多人觉得正则难其实难点往往不在单个语法而在于理解分组和引用的数据流——正则不只是做判断它还能提取内容并把内容回填到输出里。一旦你理解了这一点正则的威力就上了一层。3. 把正则用到 grep、sed、awk 里才算真正学会3.1 grep日志过滤的第一步grep 用来按正则过滤行默认输出匹配行加-E使用扩展正则。我日常最常用的组合是这几个grep -E pattern file # 匹配行 grep -v -E pattern file # 反向过滤 grep -c -E pattern file # 只统计匹配行数 grep -n -E pattern file # 显示行号 grep -o -E pattern file # 只输出匹配到的部分 grep -r -E pattern dir/ # 递归搜索目录举例grep -c status5[0-9][0-9] app.log grep -oE request_id[0-9] app.log-o参数特别推荐新手多用。它只输出匹配到的片段而不是整行适合用来确认自己写的正则是真的匹配到了想要的内容还是在靠整行里碰巧有相似字符串蒙混过关。调试正则的时候-o是你的眼睛。3.2 sed流式替换与行操作sed 的s命令是正则的核心应用场景基本形态是sed -E s/旧正则/新内容/g file不加g只替换每行第一个匹配加g替换全部用-n配合s///p可以只打印发生替换的行。sed 还可以结合正则做地址范围比如sed -n /^ERROR/,5p app.log # 打印从 ERROR 行开始的连续 5 行 sed /^#/d config.txt # 删除以 # 开头的注释行批量替换时一定要养成先预览再落盘的习惯sed -E s/old\.com/new\.com/g config.txt # 先打印到屏幕确认效果 sed -E s/old\.com/new\.com/g -i config.txt # 确认无误后再写回文件这个习惯看起来多余但在生产环境改配置的时候少一次误操作比什么都值钱。3.3 awk正则与字段结合awk 的优势是按字段处理默认按空白分割$1是第一个字段$2是第二个字段。正则用法可以这样组合awk /正则/ file # 匹配整行 awk $3 ~ /正则/ file # 匹配第三个字段 awk $3 !~ /正则/ file # 第三个字段不匹配 awk $3 ~ /正则/ {print $1, $4} file # 匹配后打印指定字段举个例子假设日志行格式是时间戳 级别 request_id... ip... path... status... cost...ms按空白分割后$4是 request_id$8是 cost。想筛出耗时超过 1000ms 的请求并输出 request_id 和耗时awk $8 ~ /cost[1-9][0-9]{3,}ms/ {print $4, $8} app.logawk 的字段概念是 grep 没有的。单纯做整行匹配grep 足够一旦需要某个字段满足条件才处理用 awk 表达比 grep 加一堆管道清晰得多。3.4 三个命令的正则差异对照维度grepsedawk默认流派BRE基础正则BREERE推荐用法grep -Esed -E直接写按行匹配支持支持/正则/地址支持/正则/按字段匹配不支持不便支持$n ~ /正则/提取并引用-o输出匹配片段s///加\1引用支持match()等是否修改文件不修改核心能力-i写回较繁琐GNU 版本的 grep 和 sed 都支持-EmacOS 自带的版本也支持所以不用担心兼容性放心用-E就好。我的习惯是能用-E绝对不用默认流派统一规则脑子不用来回切换。4. 新手翻车重灾区转义、中文和 Shell 二次解析4.1 转义失败写了正则却什么都没匹配到这是我见过最多的新手问题。典型现场是这样的在默认 grep 里写grep ab file想匹配 ab、abb、abbb结果一条都匹配不到。原因是 BRE 里是普通字符你实际找的是字面上的ab。同理grep a{2,3}也失效因为 BRE 里{ }不转义就是普通字符。反过来在 ERE 里写grep -E ab\反而匹配不到ab因为被转义成了字面加号。我整理了一张踩坑对照表新手可以直接对照自查想要的匹配错误写法正确写法一个或多个 agrep aBRE 下失效grep -E a重复 2~3 次 agrep a{2,3}BRE 下失效grep -E a{2,3}字面点号grep .匹配任意字符grep -E \.字面 IPgrep 192.168.1.1点被当通配grep -F 192.168.1.1或grep -E 192\.168\.1\.1-F参数值得记住它表示纯字符串匹配完全关闭正则解释。当你只是想找一个固定的、包含大量特殊符号的字符串时grep -F比费劲转义省事得多。4.2 中文字符与多字节匹配Linux 下的正则是按字节处理的这是很多人中文匹配出问题的根源。在 UTF-8 环境下一个汉字占 3 个字节如果直接用字符范围匹配有时会出现意料之外的结果。相对稳妥的做法有两种grep -P [\x{4e00}-\x{9fff}] file # PCRE 的 Unicode 范围写法 grep [一-龥] file # 依赖 locale 为 UTF-8 的写法顺带提醒环境变量LANG或LC_ALL被设置为C时[[:alpha:]]这些字符类只认识 ASCII 字母遇到中文会按多个字节看待匹配结果会很奇怪。排查中文匹配问题时第一步永远是先看echo $LANG的输出。4.3 Shell 先于正则读了一遍命令这是一个非常隐蔽的坑。你在命令行写grep pattern file时单引号里的内容会原样传给 grep没有问题。但如果图省事用了双引号Shell 会先对里面的$、反引号、部分反斜杠做替换然后才轮到 grep 处理正则。举例grep $USER /etc/passwd # Shell 先把 $USER 替换成你的用户名 grep $USER /etc/passwd # 才是在找字面 $USER最省心的习惯是正则一律用单引号包裹Shell 完全不碰里面的内容。只有在正则里确实需要插入 shell 变量的值时才改用双引号并且要确认变量内容里不含正则特殊符号。这个习惯能帮你规避掉一大类莫名其妙的正则不生效问题。5. 一个真实案例从日志里筛出所有异常请求5.1 需求与原始数据假设有一个应用日志app.log每行格式如下虚构数据但非常接近真实生产日志[2024-05-01 10:22:31] INFO request_id12345 ip192.168.1.10 path/api/login status200 cost45ms [2024-05-01 10:22:32] WARN request_id12346 ip192.168.1.11 path/api/search status503 cost1200ms [2024-05-01 10:22:33] ERROR request_id12347 ip192.168.1.12 path/api/order status500 cost2300ms [2024-05-01 10:22:33] INFO request_id12348 ip192.168.1.13 path/api/static/a.js status200 cost2ms需求是找出所有状态码为 5xx 或者耗时超过 1000ms的请求输出 request_id、状态码和耗时。5.2 一步步构造正则的过程第一步先筛 5xxgrep -E status5[0-9][0-9] app.log这里5[0-9][0-9]表示 5 后面跟两位数字能覆盖 500 到 599。注意不要写status5*那表示 5 后面跟任意个 5完全不是一回事。第二步筛耗时超过 1000ms 的请求。耗时字段是cost开头、ms结尾1000ms 以上意味着至少 4 位数字grep -E cost[1-9][0-9]{3,}ms app.log[1-9]保证千位不是 0[0-9]{3,}表示后面至少还有 3 位数字。这个表达式能匹配 1000ms、2300ms、12000ms不会匹配 999ms。第三步把两个条件合并。|的优先级最低所以下面的写法等价于两个各自独立的匹配条件grep -nE status5[0-9][0-9]|cost[1-9][0-9]{3,}ms app.log如果你想要的是同时满足 5xx 且耗时超时grep 就得写出一个很长的表达式不仅难看还容易出错。换成 awk 就清晰多了awk ($0 ~ /status5[0-9][0-9]/) ($0 ~ /cost[1-9][0-9]{3,}ms/) {print $4, $7, $8} app.log5.3 验证与误报排查正则改来改去最怕看起来匹配了其实匹配的不是自己要的东西。我自己的排查习惯分三步走用grep -oE只输出实际匹配到的片段肉眼检查是否符合预期。用grep -cE统计匹配行数记下来作为后续改动对照的基准。用grep -vE反向匹配看看剩下没被筛出来的行里是不是真的都不符合防止漏网。这个需求里最容易出现的误报是某一行状态码确实是 500耗时也恰好超时在或条件下它被当成一条异常请求捞出来没问题但如果需求变成分别统计5xx 数量和超时数量就必须分开grep -c否则计数会重叠。先想清楚需求是或还是与再决定用 grep 还是 awk这一步比正则语法本身更重要。6. 练习方法与老手习惯6.1 本地练习与在线练习怎么搭配本地练习材料其实到处都是。/etc/passwd文件可以练用户名和 shell 路径的匹配系统或应用日志可以练组合条件和字段提取也可以自己造一个测试文件把上面例子里的几行日志存下来反复练习。我建议把练习目标定成解决具体问题而不是把语法背完。比如今天练提取所有 IP 地址明天练批量替换配置里的旧域名每练一个场景正则脑回路就长一块。在线正则测试工具也可以用来快速验证语法、查看高亮位置特别适合搞懂贪婪和懒惰匹配的差别。但要注意大多数在线工具默认是 PCRE 风格和 Linux 命令行里 grep、sed 的默认流派有差异。网页上能通过的表达式拿到命令行不一定一样要么用 grep -P 对齐流派要么接受差异重新调整。6.2 我的几条实操经验最后分享几条这些年实际用下来的习惯都不是什么高深理论但能省下大量排查时间。第一别试图用一条巨型正则解决所有问题。把需求拆成几步先 grep 粗筛再 awk 细筛可读性和可维护性远比一行炫技重要。排障的时候你能快速理解自己上周写的命令在干嘛比什么都强。第二凡是涉及替换操作一定先不加-i跑一遍看输出确认无误后再写回文件。这个习惯救过我太多次尤其是 sed 批量改生产配置的场景。第三正则里能明确写字符范围就尽量写少用.*。日志里字段之间有固定分隔符时用[^ ]或具体的[0-9]去限定避免贪婪匹配吞掉后面的内容。宁可多写几行也别让正则的模糊度超过需求本身。第四频繁用到的匹配模式可以沉淀成 alias 或小脚本。比如经常查 5xx 状态码就加一个alias errgrepgrep -nE status5[0-9][0-9]后面直接用不用每次重新敲。关于正则的学习我印象最深的一次是第一次用 sed -E 批量替换了几十个配置文件里的旧域名几分钟完成了手工要半小时的活。从那以后我再也不觉得正则难了——它难不是难在语法多而是难在很多人一开始不知道要先分清流派、要会用单引号、要敢于用-o验证。把这三件事记住剩下的就是查表不断积累的过程了。

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

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

免费获取报价 →
↑