首页/新闻资讯/正文详情

Shell三剑客grep、sed、awk与正则表达式实战指南

发布时间:2026/9/26 12:06:49 来源:云帆数科 栏目:资讯中心
Shell三剑客grep、sed、awk与正则表达式实战指南
先聊个实际场景。你在生产服务器上拿到一份几十万行的日志老板让你统计某时段内 5xx 错误最多的接口或者你需要批量修改 200 个配置文件里某个 IP 地址再或者你想从一个结构化文本里把 URL、状态码、响应时间抽出来做成报表。这些活儿交给 Shell 三剑客 grep、sed、awk 配合正则表达式基本上就是几分钟的事。我干运维和 Devops 这么多年可以负责任地说这三个命令加一套正则语法是 Linux 文本处理真正的核心能力你写脚本、查日志、做数据清洗绕不开它们。这篇文章不打算按手册从头到尾念一遍命令参数而是基于实际使用经验把正则表达式在三剑客里的共性和差异、每个工具的典型适用场景、常见的翻车原因全部拆开。如果你是刚接触 Shell 的新手或者已经会几个命令但遇到“明明正则没错却匹配不到”的困惑这篇文章值得你把它当成一份避坑指南来读。1. 正则表达式三剑客之间的通用语言很多人一上来就问“先学 grep 还是先学 sed”我的回答是先学正则。grep、sed、awk 只是工具正则才是它们统一使用的语言。你只要把正则的字符、量词、锚点、分组这些概念理解透三个工具里至少一半的命令能力就通了。1.1 字符、字符类与预定义类的对应关系正则表达式的核心逻辑是“描述一组字符串的模式”而不是逐个字去精确匹配。最简单的形式就是普通字符比如abc就匹配字母 a、b、c 连续出现的字符串。但实际处理文本时你更多需要的是“字符类”也就是中括号表达式。[abc]表示匹配 a、b、c 中任意一个字符[a-z]表示匹配小写字母[0-9]表示匹配数字[^abc]表示匹配除 a、b、c 之外的任意字符。注意这个^放在中括号开头表示“取反”跟后面的行首锚点含义不同初学者经常在这里搞混。除了自己写字符类还有一批预定义好的简写类需要掌握\d代表数字\w代表字母数字下划线\s代表空白符\b代表单词边界。这里有一个必须明确说明的坑\d这类写法在 Perl 系正则和 grep 的-P模式里没问题但在 POSIX 字符类体系里三剑客默认模式下不一定认。在基础正则BRE和扩展正则ERE下更稳妥的写法是[[:digit:]]、[[:alpha:]]这种 POSIX 写法。我见过太多人在 awk 里用\d匹配数字结果什么都没匹配到就是因为 awk 默认不认\d你得用[0-9]或[[:digit:]]。另一个容易忽略的细节是“点”这个字符。正则里.匹配除换行符之外的任意字符所以如果你想匹配一个真正的句点必须写成\.。在日志处理时IP 地址192.168.1.1里的点如果用.去匹配也能歪打正着因为点能匹配任意字符但如果你要精确校验 IP 格式就必须写192\.168\.1\.1否则192 168 1 1这种错误内容也可能被匹配进去。1.2 量词、锚点、分组与逻辑或有了字符类还只是第一步正则真正厉害的地方在于量词。*表示前边字符出现 0 次或多次表示出现 1 次或多次?表示出现 0 次或 1 次{n,m}表示出现 n 到 m 次。组合起来[0-9]就能匹配连续数字串https?://就能匹配 http 或 https 开头的地址。量词的基础用法很好懂真正让人困惑的是它和工具正则模式之间的关系。grep 默认用的是基础正则 BRE在 BRE 下和?属于普通字符你需要写成\和\?才具备量词含义。所以你会看到老手在 grep 时总是加-E或直接用egrep就是为了切换到扩展正则 ERE这样、?、(|)这类字符就不需要额外转义了。而 sed 默认同样基于 BRE但 GNU sed 提供了-E选项启用 ERE。awk 则天然使用 ERE写法上反而最省心。锚点负责定位。^匹配行首$匹配行尾。^ERROR表示匹配以 ERROR 开头的行failed$表示匹配以 failed 结尾的行。锚点在高亮过滤时特别常用比如你要看日志里以时间戳开头的内容^2025-就能快速锁定。分组和逻辑或放在一起说因为它们在 ERE 里是绝配。(error|warn|fatal)表示匹配这三个单词中的任意一个配合量词可以构造更复杂的模式比如(error){2,}表示 error 连续出现至少两次。分组还有一个重大作用叫“捕获”被括号包住的内容可以被后续引用。在 sed 替换中\1表示第一个分组捕获的内容在 grep 的反向引用中同样适用。这个能力是做文本提取的核心比如sed -E s/([0-9])\.([0-9])/\1-\2/能把点分隔的内容改写成逗号分隔。1.3 贪婪匹配、懒惰匹配与工具差异量词默认是贪婪的这会引发一类非常经典的问题。.*会尽可能多地匹配字符在a b c这类字符串里a.*c会匹配整个a b c而不是最小的a...c片段。这在日志提取时经常造成结果过长比如你想匹配title.../title用.*可能把后面所有内容都吞进去直到最后一个/title才结束。这时候你希望用懒惰匹配也就是在量词后加?写成.*?让它匹配尽量少的字符。但问题是grep 默认的 BRE/ERE 不支持懒惰量词除非用grep -P切换到 PCREsed 在大多数版本里也不支持awk 同样不支持。所以你在三剑客里遇到“匹配过了头”的问题通常不能直接改成正则懒惰写法而是要想办法用否定字符类替代比如用[^]*来匹配“不是闭合标签”的内容这样就能达到类似懒惰匹配的效果。理解这一点在实际的数据抽取里非常关键。反向引用是另一个工具差异点。grep、sed 都支持之前提到的\1、\2这种引用方式用在替换或者提取重复词时很好用比如grep -E ([a-z]) \1能找出连续出现两次相同单词的行。但 awk 的 ERE 不支持反向引用你想在 awk 里做相同的事情需要用函数或者转成数组来比较这一点常常让从 sed 转过来的程序员措手不及。2. grep筛选与定位的行级利器当你需要从一堆文件里“过滤出符合条件的行”时第一个想到的工具就是 grep。它的工作单位是“行”输出的是完整行或者命中片段。它的优势是速度快、选项丰富、支持递归扫描目录在管道里做初步过滤时几乎是无可替代的。2.1 grep 的运行模式与返回码grep 的基本模型非常简单读入每一行尝试用正则进行匹配命中就输出。但你要注意它的三种正则模式用错了选项就会得到错误结果。默认是 BRE 模式也就是基础正则此时、?、|、()都需要转义加-E之后进入 ERE 模式写法跟 awk 一致加-P之后进入 PCRE 模式可以获得\d、\s、懒惰匹配等更现代的语法。grep 的退出码在 Shell 脚本里是个很有用的信号。匹配到任何内容返回 0没有匹配返回 1命令本身出错返回 2。这意味着你可以用if grep -q pattern file; then这样的写法做条件判断而不需要专门去统计输出行数。配合-q静默模式命令不会输出任何内容只影响返回码在脚本里做流程控制非常干净。还有一个小细节grep本身输出的是“包含模式的行”而不是“模式本身”。如果你只关心文件里有多少个独立的匹配项比如统计日志里出现了多少次 IP你可能以为grep -c ip就行了但-c统计的是匹配的行数如果一行里出现三次同一个 IP它只算一行。严格意义上的匹配次数要结合-o选项来看。2.2 高频选项从 -E、-v 到 -o、-r高频选项需要结合场景来记。-i忽略大小写适合搜索关键字时不想区分大小写的情况-v反向选择输出“不匹配”的行经常用来排除注释、排除空行-n带行号输出这个在定位配置文件问题时非常实用-o只输出匹配到的部分而不是整行是做提取任务的关键-r递归扫描目录加上--include可以只扫描特定类型的文件-l只列出包含匹配内容的文件名不显示具体内容在代码里找哪个文件用了某个函数时好用到爆。grep -o是我用得最频繁的组合之一。比如日志行是IP192.168.1.1 time23ms status500你想把 IP 全抽出来直接grep -o [0-9.]\注意在 BRE 里要转义或使用-E写作[0-9.]输出就是干净利落的 IP 列表完全不掺其他文字。这为后续管道进入排序做统计铺平了路。另一个值得记住的是-x选项它要求整行完全匹配。很多初学者不知道默认 grep 是“包含匹配”也就是行里任意位置命中模式就算命中这会导致用grep 2025 file去匹配年份时把202512这种内容也带出来。如果你要求整行只是年份就要用-x 2025或者用锚点^2025$。2.3 实战日志错误提取与代码检索先说日志场景。有一次我处理 Nginx 错误日志只关心包含PHP Fatal或PHP Warning的行最直接的方式是grep -E PHP (Fatal|Warning) error.log需要注意的是括号和竖线在默认模式下不可用所以必须加-E。进一步我只想输出下午 14 点到 15 点的致命错误日志时间戳格式是2025-01-01 14:xx:xx可以这么写grep -E ^2025-01-01 14: error.log | grep PHP Fatal这里先过滤时间段再过滤错误级别两个正则管道配合比写一长串复杂正则更直观也更容易调整条件。我个人的习惯是尽量让每条命令保持单一职责用管道组合而不是憋一个巨型正则。代码检索场景里grep -rn配合--include是最实用的组合。我自己在排查线上代码中谁调用了某个函数时通常会这样写grep -rn --include*.php send_mail( src/这样只扫描 PHP 文件避免把 vendor 目录里的第三方库也翻出来。如果你还要排除目录可以在后面加--exclude-dirvendor。用-l再加一层就能快速得到“哪些文件涉及此函数”的清单方便直接精准修改。grep 还有个小众但高频的用法多个模式匹配可以用-e参数叠加。比如grep -e fatal -e error -e timeout等价于grep -E fatal|error|timeout但-f参数可以从模式文件里读入一批正则这对需要周期性过滤某些指标的场景非常好用可以避免每改一个规则就修改一次脚本代码。3. sed流式改写与文本手术刀如果说 grep 负责“找”那么 sed 就负责“改”。它读入一行按你指定的地址范围匹配行在这些行上执行替换、删除、插入等动作然后输出。它不要求文件一次性读入内存而是像水流一样逐行处理所以处理超大文件时非常有优势。3.1 sed 的工作机制逐行读取与模式空间理解 sed 前你必须知道“模式空间”这个概念。sed 每次只从输入中读一行把它放到一个临时缓冲区域也就是模式空间然后按你给的命令依次执行操作操作完成后输出缓冲内容再读下一行。这意味着 sed 本质上是无状态的流式处理不会像编辑器那样整个文件驻留内存。这带来两个实际影响。第一是性能几十 GB 的日志文件你用 sed 批量替换完全无压力因为它的内存占用是固定的。第二是逻辑你不能在 sed 命令里指望“上一行的信息”默认留存除非使用保持空间等技术。-i选项会让 sed 直接修改原文件也可以加后缀实现备份sed -i.bak s/old/new/ file会生成一个file.bak备份再修改原文件。这个习惯我特别建议大家保留尤其在生产环境批量改配置时万一替换规则写错还能从备份恢复。3.2 寻址定界精准锁定要处理的行sed 命令的基本结构是“地址 命令”。地址可以是一行行号比如5d表示删除第 5 行也可以是一个范围比如10,20d表示删除第 10 到 20 行还可以是一个正则模式比如/^ERROR/d表示删除所有以 ERROR 开头的行。最强大的是把多个条件组合起来。/pattern1/,/pattern2/表示从匹配 pattern1 的行开始到匹配 pattern2 的行结束之间所有行都作为命中范围。比如日志中有一次请求的起始行和结束行你可以用这种方式把整段提取出来sed -n /BEGIN REQUEST/,/END REQUEST/p app.log这里的-n很关键因为 sed 默认会输出每行而这段范围之外的内容你并不想看-n会关掉默认输出只有p命令明确打印的内容才会出现。再补充一个偏移量用法/pattern/,5表示匹配到 pattern 的行以及其后 5 行都算命中。在做异常上下文查看时十分有用比如捕捉ERROR关键字以及紧随其后的堆栈信息sed -n /ERROR/,10p error.log地址之间的空格、逗号、分号这些细节虽然不起眼但差一个字母可能导致整批误操作。我的建议是在正式跑全量文件前先去head出一份小样本替换成用-n加p的模式先把命中范围验证一遍再决定要不要真正执行修改。3.3 替换、删除、插入核心命令一次讲清sed 里最常见的命令是s也就是替换。s/旧模式/新内容/会替换命中行的第一个匹配项末尾加g表示行内全部替换这一下就治好了很多人只替换第一处的疑问。如果只想要替换第二个匹配项可以用s/old/new/2这个用法比较小众但某些数据整理场景确实用得到。当你的“旧模式”本身包含斜杠时默认的分隔符/就会造成可读性问题。比如要替换路径/usr/local/bin如果写成s/\/usr\/local\/bin/new/满屏反斜杠让人眼花。这时候可以换分隔符sed 允许你用任意字符作为分隔符sed -i s#/usr/local/bin#/opt/bin#g config.conf我一般习惯用#或|具体看替换内容里哪个字符更少出现。记住一条原则分隔符只是为了让模式可读不是规定死的你完全可以选择最顺手的。除了替换d删除行、p打印行、a在行后追加内容、i在行前插入内容、c用新文本替换整行这些命令都是日常高频操作。举一个比较实用的例子给所有以#开头的注释行后面插入一行分隔线sed /^#/{a\-------------------------------- } file严格说这个命令在不同版本 sed 里写法略有差异GNU sed 对大括号和a后面的反斜杠换行有自己的格式要求所以在 macOS 自带的 BSD sed 上可能会报错。跨平台时建议测试覆盖或者改用别的方案。3.4 保持空间利用“第二块缓冲区”实现逆序与拼接sed 还提供保持空间可以理解成模式空间之外的另一个暂存缓冲区。常用命令有h把模式空间复制到保持空间、H把模式空间追加到保持空间、g把保持空间复制回模式空间、G把保持空间追加到模式空间、x交换两个空间。这个机制初看抽象但实际用得很多。比如实现最简单的文件逆序打印sed 1!G;h;$!d file.txt拆开解释1!G表示除第一行外每次把保持空间的内容追加到当前行后面h把当前行存入保持空间$!d删除除了最后一行之外的当前行。这样每一行都会挂到下一行尾部最后一行触发时保持空间里已经攒了整个文件的逆序副本打印出来就是逆序。当然现实用途不仅限于炫技。我在拼接日志内容时经常用保持空间比如把多行日志拼接成一条记录再交给 awk 统计字段这是 grep 和 awk 无法直接做到的。说到底保持空间是 sed 进阶的必经之路但我不建议一上来就背这些命令而是遇到具体需求再去查阅用多了就自然记住了。4. awk面向字段的文本计算器grep 擅长按行筛选sed 擅长按行改稿但如果你要“按列取数”“做统计求和”“按条件做聚合分组”就得 awk 出场了。awk 实际上是一种轻量级编程语言它有变量、分支、循环、数组和函数只是它的默认输入处理方式被设计得非常贴合文本表格式数据。4.1 awk 的三段式模型BEGIN、逐行处理、END任何 awk 程序都可以理解为三段在处理任何输入之前执行BEGIN块然后对每一行输入执行中间的主体代码块最后在处理完全部输入之后执行END块。比如统计访问日志的总请求数可以这么写awk BEGIN {count0} {count} END {print count} access.log在这个例子里count0其实可以省略因为 awk 的变量默认是 0但显式写出能让别人更好理解。中间代码块每次都执行count意味着每行都加一。最后打印总和。这个三段式模型有个很大的好处你可以在BEGIN里设置处理参数比如字段分隔符在END里统一输出结果。这些设置如果放在中间代码块里就会对每一行都执行一次纯属浪费性能。awk 对输入有天然的特殊变量。$0代表整行$1代表第一个字段$2代表第二个字段以此类推。默认字段分隔符是空白字符空格或 Tab如果你处理的文件是冒号分隔或逗号分隔可以通过-F参数设定例如awk -F: {print $1} /etc/passwd。这和 cut 命令很像但 awk 强的地方在于它不仅能取字段还能在取的同时做模式判断和统计计算。4.2 字段、分隔符与内置变量从 $1 到 NF 的完整链条内置变量里NF表示当前行的字段数量NR表示当前已处理的总行数FS是输入字段分隔符OFS是输出字段分隔符RS是记录分隔符ORS是输出记录分隔符。NF特别实用。比如你想取每行最后一个字段就可以用$NFawk {print $NF} file.txt这个写法比用$5或$7这种固定位置更灵活因为每行字段少的时候$NF仍然能正确取到最后一列。如果想取倒数第二个字段可以写$(NF-1)。注意这里的括号是必须的不要写成$NF-1那会被解释成“取最后一个字段的数值再减 1”。NR经常用来筛选特定行号范围。awk NR10 NR20等价于sed -n 10,20p。它还能用在调试时加一句话让你只查看相对行号的信息awk {print NR: $0}会给每行加上行号前缀。字段分隔符的设置是 awk 的核心。连续空格默认被合并处理所以awk {print $2}对文本“a b c”会干净地输出 b。如果你需要按冒号切分就得指定-F:或者写BEGIN {FS:}。如果你处理的文件本身有多个连续分隔符比如用多个空格或制表符混合的表格可以在FS里用正则来定义比如FS[ \t]awk 会按连续空白拆分。这是它对比 cut 的明显优势之一。4.3 模式匹配与流程控制让 awk 具备编程能力awk 的主体代码块前面可以加模式条件。最简单的awk /pattern/ {action}表示只处理匹配 pattern 的行。比 grep 更高级的是你可以在匹配之后直接对字段进行计算。我处理 Nginx 日志时有一类非常常见的需求统计某个 URL 的平均响应时间。日志字段大概是IP - - [time] GET /api/user HTTP/1.1 200 0.032注意到响应的数字在第 10 列左右但更可靠的方式是把 URL 在第 7 列提取把响应时间匹配出来。这里用模式匹配字段的方式更稳awk $7 /api/user {sum $10; count} END {if (count 0) print sum/count} access.log$7 /api/user就是对字段做相等判断awk 会在执行动作之前先判断这个条件是否成立。条件可以用和||组合比如$9 500 $7 ~ /login/表示状态码是 500 且 URL 包含 login 的行。另一个很有用的点是~运算符它表示匹配正则。$7 ~ /api/表示第 7 个字段包含 api$7 !~ /api/表示不包含。很多新手在 awk 里试图直接写/api/ {action}但还想限定某个字段就会误伤其他列。正确的姿势永远是明确使用字段变量。awk 还支持经典的 if-else 和 for 循环其实你已经可以用它完成复杂的编程任务。但实用主义者可以先把精力放在模式、动作和内置变量的组合上这些已经能解决 80% 的文本统计需求。4.4 关联数组实战去重、计数与求和awk 的数组是关联数组也就是说下标可以是字符串而不仅仅是数字。这个特性直接支持了“按某一列分组统计”的经典诉求。比如统计访问日志里每个 IP 的请求次数awk {count[$1]} END {for (ip in count) print ip, count[ip]} access.log这个命令读完整个文件后count数组的键是每个 IP值是该 IP 出现的次数。for (ip in count)遍历所有键并打印。如果想把结果按次数从高到低排序可以再接管道awk {count[$1]} END {for (ip in count) print count[ip], ip} access.log | sort -rn | head -20把计数放在第一列sort -rn按数字降序head -20取出前二十名。这个组合是我排查网站访问来源时最常用的命令之一。去重也是关联数组的天然应用。假如文件里有很多重复行你想统计有多少种不同的行可以直接用awk !seen[$0] file。这个写法的原理是第一次碰到某行时seen[$0]的值是 0取反!后条件为真执行打印同时让该键值变成 1第二次再碰到相同内容时seen[$0]已经是 1取反为假就不打印了。求和更简单。比如统计一个包含价格的文本文件中所有金额假设第二列是价格直接awk {sum $2} END {print sum}就行。这里要注意如果文本中混有非数字内容awk 在运算时会把它们当作 0不会报错但结果可能不太符合预期。这既是优点也是坑知道数据格式的前提下用起来非常爽。5. 三剑客协同一条命令解决一个分析任务单独用其中一个命令能解决部分问题但真正复杂的需求通常需要把三剑客串到一条管道里。这也是我推荐的学习顺序先分别精通再组合出击。5.1 分工逻辑grep 做减法sed 做整形awk 做统计三剑客的组合逻辑可以简化为一句话grep 负责缩小范围sed 负责改变行内容awk 负责提取和计算。你不需要让 grep 完成统计也不需要让 awk 去干筛选的活各司其职代码会更清晰性能也更好。一个典型的流水线是先用 grep 从整个日志文件里过滤出“某个特定时间段”或“包含某个关键字”的行这些行的数量往往已经缩小到全量的百分之一接着用 sed 对筛选结果做一些格式整理比如去掉多余前缀、把分隔符统一最后用 awk 做频率统计、求和、求平均。有人可能会问为什么不能全用 awk 写确实awk 也能做正则匹配甚至能把整个过程写进一段 awk 程序里。但实际维护时分成几个管道步骤更容易调试。你可以随时在某一步停下来看看输出确认过滤条件是否正确而一个几十行的 awk 程序一旦某一步出问题排查起来反而麻烦。5.2 实战案例统计访问日志 Top 10 的来源 IP拿我前面提到的 Nginx access log 为例假设字段格式是1.2.3.4 - - [10/Jan/2025:12:00:01 0000] GET /api/user HTTP/1.1 200 0.02第一步先用 grep 过滤掉健康检查请求比如排除GET /health的记录grep -v /health access.log | grep GET | awk {print $1} | sort | uniq -c | sort -rn | head -10这里grep -v /health排除健康检查第二个grep GET只保留 GET 请求awk {print $1}提取 IP 字段sort把相同 IP 聚到一起uniq -c按 IP 计数sort -rn按次数降序head -10取前 10 名。有人会想这一步直接用awk $7 !~ /health/ $8 GET {count[$1]} END ...一个命令就做完了。你说得对这种方式完全可行而且更高效。我的建议是如果你已经熟练掌握了 awk可以优先用 awk 一把梭如果还在学习和排错阶段拆成管道步骤更好每步都能单独验证。5.3 实战案例批量修改配置文件且不乱动无关内容批量修改配置文件是 sed 的主场。假设一个目录下有几十个 config 文件要把里面的旧数据库地址192.168.10.10替换成新的192.168.20.20而且只想动配置行不想误伤日志文本里的同名 IP。可以这样组合find conf/ -name *.conf -type f | xargs sed -i.bak s/192\.168\.10\.10/192.168.20.20/gfind负责递归找到所有.conf文件xargs把这些文件名传给 sedsed -i.bak执行原地替换并保留备份。注意我在正则里把点写成\.避免一个点匹配任意字符导致192x168x10x10这样的错误内容也被替换。如果只改特定配置块里的内容比如只想修改[database]段落的 host就需要把 sed 的寻址能力用上sed -i.bak /\[database\]/,/^\[/{s/192\.168\.10\.10/192.168.20.20/g} config.ini/\[database\]/,/^\[/表示从包含[database]的行开始到下一个以[开头的行结束在这个范围内执行替换。这里一定要转义[因为[在正则里是字符类开头不转义会让匹配直接失效。这类批量操作最容易出问题的地方是“范围没卡准”。我在用这种模式之前一定会先用sed -n /\[database\]/,/^\[/p config.ini打印一下范围的边界确认没有把下一节的内容框进来再执行真正的替换。6. 不踩坑的 Shell 文本处理常见问题与速查表即使你掌握了命令语法实战中还会撞上各种“看起来没毛病运行结果就是不符合预期”的情况。大部分问题都集中在引号、正则模式差异和字符编码这几个环节。6.1 引号问题为什么你的正则到了脚本里就失效Shell 对引号的处理是新手进入文本处理领域的第一道坎。在双引号里$、反引号、\等字符会被 Shell 解释而在单引号里所有字符都保持字面含义。最常见的错误是写grep abc$ file时你本意是匹配以 abc 结尾的行但双引号里的$被 Shell 当成了变量开头如果变量不存在它就变成空字符串命令变成匹配abc结果可能把abcd也带出来了。正确的写法应该是grep abc$ file用单引号保护正则里的$。同理sed 替换时如果你使用双引号并包含\1Shell 可能会对反斜杠做转义导致反向引用失效。在不需要 Shell 展开变量时尽量全用单引号。如果确实需要把 Shell 变量传入正则比如patternerror你可以用双引号包裹整个命令但变量内包含正则元字符时Shell 不负责转义你需要在变量中预先写好合规的正则字符串。另一个相关坑是grep $pattern中$pattern的值如果是a b它不是被当成“匹配 a 或 b”整个字符串会被当作普通模式处理。如果你想按正则语义展开变量内容最好先确认变量里已经包含写好正则元字符的模式。6.2 BRE、ERE、PCRE 差异同一正则在三个工具里结果不同这是三剑客正则最大的混乱源头。grep 默认 BREsed 默认 BREawk 默认 EREgrep -P 才启用 PCRE。同样的写法在不同模式下语义完全不同。举几个实际例子a在 BRE 中匹配字符a加号是普通字符在 ERE 或 PCRE 中匹配一个或多个 a。(abc)在 BRE 中会被当作普通字符括号除非写成\(abc\)在 ERE 或 PCRE 中才是分组。\d在 BRE/ERE 中通常不是数字只在 PCRE 中才等于[0-9]。懒惰量词*?在 BRE/ERE 中都不具备懒惰语义在 PCRE 里才有效。所以当你把一段从网上复制来的正则粘到 grep 里却匹配不到时优先检查你是不是用了默认 BRE而它依赖了或|。我个人的习惯是在 grep 和 sed 里统一加-E除非我明确需要 BRE 的特性否则绝不依赖默认模式。6.3 文件编码与多字节中文的正则匹配Shell 文本处理默认是面向字节的。这意味着当你处理的文本包含中文等多字节字符时普通字符类可能无法按“单个中文字符”去匹配。最常见的现象是你想匹配日志里的中文关键字结果用[一-龥]这种字符范围失败或者用.匹配时仅吞掉中文的一部分字节造成输出乱码。在 GNU grep 支持-P的情况下可以用 PCRE 的 Unicode 属性写法比如匹配所有中文字符grep -P [\x{4e00}-\x{9fa5}] file。如果不支持就可以先用locale确认环境编码是 UTF-8然后尽量直接匹配具体中文字符串而不是使用字符范围。awk 处理中文时用index()函数判断子串或者直接用$0 ~ /具体关键词/通常比依赖字符类更可靠。6.4 高频问题速查表我把平时最常遇到的一些问题整理成表格方便你快速对照。现象常见原因解决方式grep 匹配不到预期的默认 BRE 模式把当普通字符加-E或用\sed 没改到文件却说执行了sed 不改原文件只输出到标准输出使用-i原地修改或重定向到新文件grep 输出一大堆无关内容默认是包含匹配不是整行精确匹配用-x或加^$锚点awk 里用\d匹配不到数字awk 的 ERE 不认识\d改用[0-9]或[[:digit:]]sed 替换路径时反斜杠密密麻麻默认分隔符/与路径冲突换成#或 脚本里 sed 的变量不生效单引号中的变量不会被 Shell 展开改用双引号或拼接变量注意转义匹配中文失败或乱码按字节处理未考虑字符编码用grep -P的\x{4e00}形式或直接匹配中文文本grep 在文件为空时返回 1没有匹配退出码 1 是正常状态在脚本中用 if 条件处理而非直接查看返回值sed -i 在 macOS 上报错BSD sed 的-i必须带备份后缀用sed -i.bak或安装 GNU sed管道中统计结果顺序乱awk 数组遍历顺序不固定使用sort/sort -rn或管道再排序这些坑我自己几乎全部踩过。最常出现的不是正则本身写错而是“工具模式”和“Shell 引号”这两个外围因素搞乱了一切。最后分享一个我个人的实操习惯所有正则表达式尤其是比较长、包含分组的模式我都会先拿echo 测试文本 | grep -P ...或echo 测试文本 | sed -E ...先做小样本验证确认匹配范围完全符合预期后再放到全量文件上跑。跑全量前如果涉及修改原文件我会先备份或者在副本上测试。这样做的成本很低但能挡住绝大多数“命令写砸了”的事故。你以后在 Shell 里处理文本时如果遇到结果诡异的问题不妨先回到这份速查表再回想一下引号和模式这两个最常见的变量十有八九能找到答案。

相关推荐

Jev本地部署实战:TypeSafe SDK + llama.cpp轻量AI运行时
Jev本地部署实战:TypeSafe SDK + llama.cpp轻量AI运行时

1. Jev到底是什么?先别急着部署,搞清定位再动手 Jev不是某个具体的大模型,也不是像Llama、Qwen、DeepSeek那样公开发布权重的开源模型。它是一个面向企业级AI应用开发的 TypeSafe AI SDK平台 ——关键词“TypeSafe”在这里不是泛泛而谈的类… · 2026/9/26 12:06:49

Shell三剑客 grep sed awk 实战指南:正则组合拳高效处理文本
Shell三剑客 grep sed awk 实战指南:正则组合拳高效处理文本

我一直觉得,Shell 下面处理文本,真正拉开效率差距的不是你背了多少命令,而是有没有把正则表达式和 grep、sed、awk 当成一套组合拳来用。很多人单个命令都认识——grep 会过滤,sed 会替换,awk 会取列——但一到真实场景… · 2026/9/26 12:06:49

Git合并冲突:从理解HEAD指针到从容解决与预防
Git合并冲突:从理解HEAD指针到从容解决与预防

我第一次见识到<<<<<<< HEAD的时候&#xff0c;整个人是懵的。那天我敲下git push&#xff0c;git 却弹出一屏CONFLICT (content)&#xff0c;然后我打开代码文件&#xff0c;发现里面长满了尖括号和等号线&#xff0c;像有人在我的代码里打了一排排路障。… · 2026/9/26 12:06:49

Ubuntu 24.04 LTS 初始化脚本:模块化实现运维自动化与开发环境一键配置
Ubuntu 24.04 LTS 初始化脚本:模块化实现运维自动化与开发环境一键配置

1. 为什么我写了一份 Ubuntu 24.04 LTS 初始化脚本如果你跟我一样&#xff0c;隔三差五就要在服务器商后台点几下鼠标、开一台全新的 Ubuntu 24.04 LTS&#xff0c;或者在虚拟机里反复折腾系统&#xff0c;大概率已经受够了那套“装系统五分钟&#xff0c;配环境半小时”的流程… · 2026/9/26 12:46:13

Claude代码工作流引擎:CLI驱动的本地化模板执行协议栈
Claude代码工作流引擎:CLI驱动的本地化模板执行协议栈

1. 项目概述&#xff1a;这不是一个“模板库”&#xff0c;而是一套可执行的 Claude 代码工作流引擎“claude-code-templates”这个名称极具迷惑性——它听起来像是一堆静态的.js或.py文件&#xff0c;放在 GitHub 上供人下载、复制、粘贴。但如果你真这么理解&#xff0c;接下… · 2026/9/26 12:46:13

区域综合能源系统双层优化调度与需求响应Matlab实现
区域综合能源系统双层优化调度与需求响应Matlab实现

这段时间又帮人复现了一篇关于“计及需求响应的区域综合能源系统双层优化调度策略”的核心期刊论文&#xff0c;顺手把整个思路和踩坑过程整理一遍。这种复现任务在研究生阶段特别常见&#xff0c;尤其是和 区域综合能源系统、需求响应、双层优化调度、Matlab 代码实现 相关的论… · 2026/9/26 12:46:13

全链路论文写作指南:从选题到查重的高效方法论
全链路论文写作指南:从选题到查重的高效方法论

1. 先想明白“全链路”这三个字&#xff0c;论文就不会乱1.1 毕业论文难的不是某个单点&#xff0c;而是环节之间的衔接我带过的本科生里&#xff0c;十个有八个在论文这件事上的崩溃路径高度相似&#xff1a;花两周搞定选题&#xff0c;又花两周把文献丢进资料夹&#xff0c;然… · 2026/9/26 12:46:06

第五篇:与 LLM 对话 —— 用 TaoToken 统一 Key 封装模型接口与 Prompt 工程
第五篇:与 LLM 对话 —— 用 TaoToken 统一 Key 封装模型接口与 Prompt 工程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 12:46:00

用 Cursor 生成 3D 学习文档:TaoToken 统一 Key 接入与配置骨架
用 Cursor 生成 3D 学习文档:TaoToken 统一 Key 接入与配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 12:45:53

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介&#xff1a;万常选版《数据库原理与设计》课后习题答案资源&#xff0c;覆盖第2至6章及第9章&#xff0c;适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件&#xff0c;含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故&#xff0c;是很多团队绕不过去的坎。线上环境里&#xff0c;服务端明明已经上线了新版接口&#xff0c;老的移动端还在照着旧文档传参数。请求一到网关&#xff0c;校验直接拒绝&#xff0c;用户操作失败&#xff0c;客服群炸了锅&#xff0c;开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码