1. 选型逻辑:grep是过滤器,sed是编辑器,awk是微型编程环境
我第一次接触这三个命令的时候,跟大多数人一样把man page翻了个遍,grep、sed、awk各自有哪些参数背得滚瓜烂熟,可真到生产环境处理几千万行的日志时,发现最难的从来不是记参数,而是拿到一个任务后,脑子里能快速判断“这一步该用哪把刀”。
先做个粗暴但不失准确的类比:grep是一个只负责“留还是不留”的过滤器,输入一行,匹配就留下,不匹配就扔掉,整个过程不修改行内容;sed是一台逐行扫描的流式编辑器,它可以读入一行、修改这一行、再输出这一行,强调“改”;awk则是一个面向“行和列”的微型编程环境,它会把每一行拆成字段,然后允许你用变量、条件、循环去计算和重组这些数据,强调“算”。
很多人在脚本里把三个工具混着用,比如用grep筛完之后再用sed替换,这没问题,但反过来,如果一个任务本质上只是“看看某段文本在不在”,用awk写了一大段BEGIN和END,就是杀鸡用了牛刀。记住,三个工具不是同一件事的三种写法,而是三种不同性质的事情。
我用过一段时间之后,整理了一条自己的选型判断链,基本能覆盖日常绝大多数场景:
- 只要判断某行是否存在、按正则过滤整行、看上下文 → 用grep;
- 需要修改行内的文字、按行号或正则定位后增删改单行内容 → 用sed;
- 需要按列拆分、统计、去重、拼接、跨行格式化输出 → 用awk;
- 需要先按条件筛选,再对筛选结果做统计 → 先grep再awk,管道接力。
这条链解决了我80%的选型纠结。剩下20%是模糊地带,比如“把文件中匹配到的行全部删除”这个需求,grep -v、sed '/pattern/d' 都能做,这时选哪个其实取决于下游逻辑和性能要求,后面我会专门展开讲。先把选型逻辑定了,后面每一个实战场景才不会跑偏。
1.1 三者各自的“工作记忆”差异
理解三剑客差异,一个很有用的视角是“看它们记住了什么”。
grep几乎不记任何状态,它看到一行,迅速做出“留/不留”的判断,判断完扔给标准输出,下一行又是全新的开始。所以grep非常轻,速度极快,这是它在大文件面前依然能保持高性能的根本原因。
sed有两块记忆区域:模式空间(pattern space)和保持空间(hold space)。默认情况下,读入一行放入模式空间,执行完脚本后输出,然后清空。保持空间是备用的“草稿纸”,平时空着,你可以用 h/H/g/G/x 等命令把内容在模式空间和保持空间之间搬来搬去。这种“双空间”设计让sed具备了简单的跨行处理能力。
awk的记忆就丰富多了:它有内建变量(NR、FNR、NF、FS、OFS)、用户自定义变量、关联数组,还有BEGIN/END两个生命周期钩子。每一行读进来之后,awk会按照字段分隔符(FS)把行拆成多个字段,存进$1、$2……$0这些变量里,然后执行你的程序体。BEGIN块在处理第一行之前执行,END块在处理完最后一行之后执行。这意味着你可以把整个文件的统计结果攒到一个数组里,最后一次性输出。
1.2 一条可以写在工位上的选型判断链
判断链上面写过简化版,这里补充一下实际操作中我会想得更细的维度。
第一,看“行”在这个任务里是否还保留原貌。如果答案永远是整行透传,只做筛选,那优先grep;如果行内的字段要被打散重拼,grep的-E再强也没用,awk的默认按列拆分才是自然解。我见过太多同事用一串sed替换去模拟列重排,结果边界条件一大堆,最后重写成awk只用了三行。
第二,看“跨行逻辑”的复杂度。只是“匹配A行之后的紧接着若干行”这种场景,sed的地址范围和N命令可以搞定;但如果是“统计每个IP出现的次数”这种需要累积的跨行逻辑,sed的保持空间写起来别扭程度暴增,awk数组一行就能完成。
第三,看下游处理是否还要二次统计。grep的输出是纯文本流,如果你筛完还要按列汇总,终究逃不过awk;那不如一开始就把条件写进awk的模式里,省一次全量扫描。这一个省字,在千万行级别的文件上,就是几秒钟和十几秒钟的差距。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. grep实战:从“能匹配”到“会协同”——上下文、递归与文件名
grep最基础的能力是匹配输出,这部分大部分人都熟,我不打算再念一遍手册。真正让我觉得grep“会用”和“不会用”拉开差距的,是三个高频但常被忽略的方面:上下文输出、递归搜索的文件筛选、以及输出文件名相关的能力。
先说上下文。排查线上故障时,日志里报错的那一行只是线索起点,真正有用的是它前后十行里的调用链和变量值。grep -A 10 -B 5 'ERROR' app.log 能一次性把匹配行以及它后面10行、前面5行全部打出来。这里有个容易忽略的坑:-A/-B 后面跟的数字是“行数”,不是“字节数”,而且匹配多行时上下文不会重复叠加,多区间重叠处会自动合并,输出顺序保持原文件顺序,这个合并行为不是所有文本工具都有的。
再说递归搜索。在项目代码目录里找某个函数在哪些文件被调用,最直接的是 grep -R。但我强烈建议不要裸用-R,因为一旦目录里有日志、图片或者构建产物,输出会被二进制匹配信息刷屏。我日常写脚本会带上 --include 和 --exclude:
bash复制grep -R --include='*.py' --exclude='*_test.py' 'def parse_' ./src/
这两个参数能精确锁定文件类型,把无关文件挡在扫描之外,速度提升也非常明显。如果只想看哪些文件命中了,而不关心具体命中哪一行,用 -l(只列文件名);反过来,想知道哪些文件里没有命中,用 -L。这两个参数在自动化检查类脚本里价值极高。
2.1 匹配之外:上下文输出的协作用法
举一个真实场景。监控系统告警说应用响应时间飙升,开发让你去查日志里所有耗时超过阈值的请求。你很快写出一句:
bash复制grep -E 'cost_time=[0-9]{4,}' app.log
这条命令能筛出耗时四位数的记录,可问题是你拿到的是孤立的一行,看不到当时的入参、调用链、缓存命中情况。实际排查时,我会这样组合:
bash复制grep -B 2 -A 5 'cost_time=[0-9]{4,}' app.log | head -200
先看匹配行前面的请求入口和参数,再看后面的处理流程日志,把每一次慢请求的“上下文切片”完整捞出来。-B和-A同时带上还有一个好处:从输出里你能发现慢请求经常扎堆出现,这对判断“是单点慢还是整体卡顿”很有帮助。
另外一个上下文输出的使用技巧是配合 grep -n,把行号也带出来。后面要定位原文件的具体位置,或者要用sed去修改那一段时,行号就是导航坐标。
2.2 递归搜索时别忘记把二进制挡在门外
很多人以为 -R 就是“在子目录里找”,实际上它扫描的范围比你想象的大得多。默认情况下,grep遇到疑似二进制文件会输出“Binary file xxx matches”,如果你的目的是找文本引用关系,这个提示既占输出又容易吓到脚本。
我个人的习惯是:搜索代码时显式指定 --include,从源头避免二进制文件被扫到;搜索日志时加 -a 强制把文件当文本处理,因为有些日志文件里偶尔混入控制字符,grep可能就把它判定为二进制了。还有一个常被忽略但很实用的参数是 --exclude-dir,像 .git、node_modules、build 这些目录,几乎每次递归搜索都想跳过:
bash复制grep -R --exclude-dir={.git,node_modules,build,dist} 'search_keyword' .
在大的代码仓库里,有没有排除掉这些目录,耗时差异经常是两倍以上。
2.3 grep -c到底统计了什么
写监控脚本时,很多人习惯用 grep pattern file | wc -l 来数匹配行数。多数情况下没问题,但如果文件末尾缺一个换行符,wc -l 会少算一行,这在处理某些不规范日志时会给你一个虚假的计数。更稳妥的是直接 grep -c pattern file,它统计的是“匹配的行数”,不依赖换行符,也不会多消耗一次管道进程。
还有一个小细节:grep -c 统计的是匹配行数,不是匹配次数。一行里出现五次ERROR,grep -c 也只计数一次。真需要统计次数的时候,应该用 grep -o pattern file | wc -l。这两者的区别,写数据校验脚本时特别容易踩。
3. sed实战:替换只是基本功,保持空间才是分水岭
sed最常用的功能是替换,这一点几乎所有教程都会讲。但如果你只用 sed 做 s/old/new/,那这就是一把刀拿来当锤子使。真正让 sed 有不可替代价值的,是它借助模式空间和保持空间实现的“跨行”处理能力。
先给替换做个“速查记忆”:s 命令的完整形态是 地址范围 + s + 分隔符 + 匹配 + 替换 + 修饰符。地址范围可以是一个行号、一个正则、或者两个地址之间的区间,比如:
bash复制sed -n '10,20s/foo/bar/p'
这行的意思是:只在第10行到第20行之间做替换,-n 关闭默认的逐行打印,p 修饰符让替换发生的行打印出来。日常中我常犯的错误是忘记带 -n,结果替换行和未替换行一起输出,日志刷得没法看。
替换部分的隐藏细节也不少。& 代表“整个匹配到的内容”,这在每个匹配项前面统一加前缀时很好用:
bash复制sed 's/ERROR/\& [严重]/g' app.log
注意这里的 & 要转义成 &,否则 sed 会把它当普通字符处理还是正则的元字符?不同版本表现不一致,GNU sed 里 & 必须写成 &。
分组引用 \1、\2 则是把正则里的括号捕获拿出来重组,比如在CSV行里把前三列顺序打乱重排,一条 sed 就能完成。
3.1 地址范围是sed的定位系统
范围选定比简单的全局替换实用得多。生产环境里,我经常需要“只修改配置文件某个段落中的某项参数”,这时候sed的地址区间就派上用场了:
bash复制sed -i '/^\[database\]/,/^\[/ s/^host=.*/host=192.168.1.10/' config.ini
这段命令匹配从 [database] 这一行开始,到下一个以 [ 开头的段落行结束,在这个区间内做替换。这个操作在关键节点或配置批次变更时非常常用,它保证了你在同一个文件里有多个类似段落的场景下,不会改错目标。
地址范围的另一个用法是取行区间,例如 sed -n '100,200p' 直接输出100到200行。在一些超大的预处理输出文件中,用它切出中间片段,比用head和tail来回接方便。
3.2 保持空间:把分散的行拼接起来
保持空间是我觉得 sed 最不好理解、但一旦理解就能写出很漂亮命令的部分。简单说,模式空间是“当前正在处理的行”,保持空间是一块独立存储区。
常用的命令有:
- h:把模式空间复制到保持空间(覆盖);
- H:把模式空间追加到保持空间(换行分隔);
- g:把保持空间复制回模式空间(覆盖);
- G:把保持空间追加到模式空间(换行分隔);
- x:交换两块空间。
一个典型应用:把一个匹配行的下一行拼接到该行末尾输出。比如日志里异常信息被拆成了两行,第一行是“ERROR: xxx”,第二行是详细堆栈,想合并成一条来处理:
bash复制sed -n '/ERROR/{h;n;H;g;s/\n/ /p}' app.log
拆解一下这条命令的动作:匹配到包含ERROR的行,把这一行存入保持空间(h),然后读取下一行(n),把下一行追加到保持空间(H),再把保持空间整体搬回模式空间(g),最后把中间的换行符替换成空格并打印。整个过程在一行里完成了跨行读取和合并,这是sed模式空间和保持空间配合的典型教科书。
3.3 多行模式N、D、P的跨行匹配
除了 h/H/g/G/x 这套“搬运”命令,sed 还有一套多行操作命令 N、D、P,分别对应“读下一行追加到模式空间”“删除模式空间的第一行”“打印模式空间的第一行”。
N命令让模式空间里同时存在两行以上,这之后的正则匹配就可以跨换行符进行了。比如要匹配“start”之后紧接着一行包含“end”的情况:
bash复制sed -n '/start/{N;/end/p}' input.txt
匹配到start后,用N吞入下一行,此时模式空间里是两行文本加一个换行符,再判断里面是否包含end。这种能力在做多行日志的归并时有奇效,但要注意N命令在文件末尾读不到下一行时会直接退出脚本,如果文件最后一行恰好匹配start,这种边界情况要有个心理预期。
4. awk实战:围绕NR/FNR与BEGIN/END的字段处理思维
awk是三个工具里最像“编程”的一个,它每一行都按照“读入→按分隔符拆分字段→执行模式匹配判断→执行程序体”的流程运转。它最核心的价值,是把一个数据流转换为结构化的“行+列”模型,然后你可以在这个模型上做各种加工。
学习awk,我建议不要第一时间去背 printf 格式串,而是先理解三个生命周期:
- BEGIN块:任何行读入前执行。这里最常干的事情是设置字段分隔符 FS、输出分隔符 OFS、初始化计数器变量;
- 主处理块:每一行执行一次。所有统计、判断、清洗逻辑都放这里;
- END块:所有行处理完后执行。汇总结果、打印报表,都在这里做。
我最初的几个awk脚本都是把逻辑全部堆在主块里,后来才意识到 BEGIN/END 的价值:BEGIN 就像函数声明区,END 就像回收汇总区。把初始化和收尾分开,代码的可读性和可维护性直接上了一个台阶。
4.1 BEGIN里定义好分隔符,比到处写-F强
新手写awk,一上来就是 awk -F',' '{print $1}',遇到复杂分隔符还会写 -F'[ ,]+'。这种方式能用,但如果你在脚本里多处用到不同分隔符,或者分隔符本身需要动态变化,-F就很笨拙。
我更推荐在BEGIN块里统一赋值:
bash复制awk 'BEGIN{FS="[ ,]+"; OFS="\t"} {print $1, $2, $3}' data.txt
FS 是输入字段分隔符,OFS 是输出字段分隔符。把两者设好以后,print $1, $2 就能直接用OFS拼起来,不用手写 ","。另外一个很关键的点是:FS="," 和 FS="[ ,]+" 的语义差异很大,后者把“一个或多个逗号或空格”都当成一个分隔符,处理不规整数据时几乎必备,而前者的 $1 可能脏到完全不能用。
这里还有一个容易被忽略的知识点:当awk把一行按FS拆完以后,$0 代表整行,$NF 代表最后一个字段,NF 代表字段数量。$0 本身还可以被重新赋值,一旦被赋值,整行会被重新按FS拆分,这是一种不常用的“字段重建”技巧。
4.2 NR与FNR:多文件统计最容易踩的差异
NR 和 FNR 看起来都是“当前行号”,但范围不同。NR 是 awk 从开始处理以来累计的总记录数,FNR 是当前文件内部的记录数。处理单个文件时两者完全一样,多文件时就是天壤之别。
举个例子:我需要把两个日志文件各自的错误行打印出来,还想知道每个文件中错误号是多少:
bash复制awk 'NR==FNR{first[$1]=1; next} /error/{print FILENAME": "FNR": "$0}' file1.log file2.log
这里的 NR==FNR 是处理多文件时一个经典判断技巧:当 NR 等于 FNR 时,说明awk正在处理第一个文件。利用这个条件,可以先把第一个文件的内容存进数组,再在第二个文件中进行关联匹配。两个文件做联查时,这一招几乎是必用的。如果不熟悉这个技巧,很多人会陷入“第二个文件的内容永远对不上”的困惑。
FILENAME 变量也很实用,它记录当前正在处理的文件名,多文件处理时输出归属关系一目了然。
4.3 经典去重写法为何是 !seen[$0]++
awk去重有一个流传很广的写法:
bash复制awk '!seen[$0]++' input.txt
第一次看这行命令的人都会懵:这到底是怎么判断的?其实拆开来看就清楚了。awk 的表达式可以作为条件,条件为真假决定当前行是否触发后续操作(这里没有后续操作,默认打印整行)。$0 是整行,seen 是数组,++ 是自增。
关键在于这行里 ! 和 ++ 的执行顺序:seen[$0] 这一项如果之前没有赋值,取值是空,在算术上下文里等价于0。!0 等于1,为真,所以第一行会被打印,同时 ++ 把 seen[$0] 变成了1。第二次遇到相同行时,seen[$0] 已经是1,!1 等于0,为假,这一行就不打印了。核心就是:首次出现的行条件为真,重复出现的行条件为假。
这个写法在去重大文件时比 sort -u 快,因为省掉了排序过程,只需要内存存下所有“出现过的行”。当然代价是内存占用随唯一行的数量增长,几十G的日志去重时,要留意机器内存够不够,否则swap一上来反而更慢。
4.4 用printf对齐输出生成报表
awk 的 print 只能输出简单的拼接结果,要生成对齐的报表,就得用 printf。printf 的工作方式来自C语言,格式串里 %s 对应字符串、%d 对应整数、%-20s 表示左对齐宽度20个字符,%8d 表示右对齐宽度8个整数位。
我在做日志统计时最常用的组合是:
bash复制awk '{count[$1]++} END{for (ip in count) printf "%-20s %8d\n", ip, count[ip]}' access.log | sort -k2 -rn | head -10
这段的输出效果是IP左对齐、次数右对齐,直接就是一份像样的TOP排行。这里要提醒一下,awk的数组遍历 for (ip in count) 的顺序是不保证的,所以打印之前用管道接sort去排序是常规操作,不要指望awk自己按你定义的顺序输出。
5. 三剑客组合拳:日志统计、定点改配与报文转CSV
单个工具用熟练后,真正生产力大增的地方是把它们串在管道里,组成一条完整的文本处理流水线。下面分享三个我在真实工作中反复用到过的组合场景,每个场景都有完整的命令和执行逻辑拆解。
5.1 场景一:Nginx访问日志的TOP IP和状态码统计
分析Nginx日志时,最经典的诉求是“哪些IP访问最多”和“返回5xx错误集中在哪些接口”。日志单行格式类似:
code复制192.168.1.23 - - [10/Oct/2024:13:45:22 +0800] "GET /api/order HTTP/1.1" 200 1024
按空格拆分后,$1是客户端IP,$9是状态码。我的第一版统计命令长这样:
bash复制awk '{code[$9]++; ip[$1]++} END{print "状态码分布:"; for (c in code) print c, code[c]; print "TOP IP:"; for (i in ip) print i, ip[i]}' access.log | sort -k2 -rn | head -20
这里awk完成了核心的统计动作,数组同时记录状态码和IP两个维度,遍历输出后再交给sort按第二列数值逆序排列。
如果想看5xx错误的接口URL,可以先把响应码是5xx的行筛出来。这时grep本身是做不了数值范围判断的,但awk的模式可以直接写:
bash复制awk '$9 >= 500 {print $9, $7}' access.log | sort | uniq -c | sort -rn | head -10
$7是请求路径。每次看到“用一条awk替代了‘grep结合正则的繁琐拼接’”,我都觉得这才是正确的工具选型。
5.2 场景二:只改配置文件中某个段落的定点替换
假设你有一个服务配置文件,里面有多段配置,每段以方括号开头。现在要把 [database] 段的连接字符串改成新的主库地址,但绝不能动其他段里的同名参数。
用sed的地址范围就能精确到段落内替换:
bash复制sed -i '/^\[database\]/,/^\[/ s#^host=.*#host=10.0.0.11#' service.conf
写这段的时候有一个容易踩的坑:地址范围结束条件 /^[/ 会匹配 [database] 本身,所以如果你把结束条件写成 /^\[/,范围会在匹配到 [database] 这行时就立即结束,因为结束地址在当前行也满足。实际上GNU sed的地址范围处理方式是:从匹配开始地址的行开始进入区间,然后逐行检查结束地址,遇到 [database] 之后的第一行若符合结束条件才会关闭。这里我实际验证过,/^[database]/,/^[/ 可以正确覆盖到下一个以[开头的段落之前,因为区间是在处理到 end地址 的同一行时结束。
为了让这个命令更鲁棒,我通常会把结束条件写得更具体一点,比如匹配另一个明确的段落名:
bash复制sed -i '/^\[database\]/,/^\[cache\]/ s#^host=.*#host=10.0.0.11#' service.conf
这样能保证即使段落顺序调整,目标也不会飘。改动完成后,用 grep -A 5 '^[database]' service.conf 确认一下替换是否生效,这个验证习惯建议每个做变更的人都有。
5.3 场景三:多行报文转CSV的管道接力
有一种场景是日志里每条业务记录被拆成了多行,比如:
code复制订单号: 10001
用户: zhangsan
金额: 199.00
---
订单号: 10002
需要把每条记录合并成一行CSV。只靠sed或者只靠awk,命令写起来都不太顺手,但它俩一配合就很清爽。思路是先用sed把三条记录的尾部换行处理成自定义分隔符,再用awk把CSV列重排,然后用head/column做展示。
第一步,把空行和“---”都转换为特殊分隔符(以 \t 为例):
bash复制sed -n '/订单号/{s/.*: //; h; n; s/.*: //; H; n; s/.*: //; H; g; s/\n/,\t/g; p}' orders.log
这命令里,h 把订单号存到保持空间,接下来两行用 n 读取后依次用 H 追加,然后用 g 全部取回,最后把换行替换成逗号。输出就是一行一行的“10001, zhangsan, 199.00”。
如果后续还要按金额排序或者做汇总,再接一个awk:
bash复制... | awk -F',' '{gsub(/^[ \t]+|[ \t]+$/, "", $3); sum += $3} END{print "总金额:", sum}'
gsub 用来清理字段首尾的空格和制表符,sum累加做汇总。这种“sed负责结构重整,awk负责计算”的分工,是组合拳的精髓,谁擅长什么谁就做那一环,而不是勉强让单个工具把全流程扛下来。
6. 几个看起来对但真跑起来会翻车的坑,以及性能取舍
到生产环境跑过才知道,文本处理工具很多“看起来正确”的用法,在特定条件下会给出完全不同的结果。我把踩过的坑和实际性能测试的经验集中写在这里,每一个都是真实场景,值得反复看。
6.1 sed -i的“原地修改”是骗人的
很多人以为 sed -i 是“在文件原地修改”,其实它的实现是:创建一个临时文件,写入处理后的内容,然后rename覆盖原文件。听起来没关系,但有两种情况特别容易出问题。
第一种是符号链接。如果你 sed -i 处理的是一个软链接指向的配置文件,操作完成后软链接会被替换成普通文件,链接关系被破坏,后续其他程序通过软链接访问时可能读到旧内容。我遇到过配置管理工具里软链被sed改坏的事故,排查半天才发现是inode都换了。这种场景下,先用 readlink 确认链接关系,或者直接先解引用再修改。
第二种是正在被进程写入的日志文件。sed -i 替换掉的是inode,但之前已经打开这个文件写入的进程,仍然握着旧的inode,后续继续写日志就写到旧文件里了,你看到的文件不会再变大,但磁盘空间不降,日志丢失。所以,热日志文件基本不用 sed -i,最多用 sed 处理后再重定向到新文件,例如:
bash复制sed '/pattern/d' app.log > clean.log
6.2 大文件场景下三把刀的性能差异
我曾经对一个3.6GB的日志文件做过一次简单的性能对比:同样的“筛出包含ERROR的行并统计每行出现的次数”这个任务,分别用三种思路实现。
- 思路A:grep 'ERROR' app.log | awk '{count[$0]++} END{for (k in count) print count[k], k}'
- 思路B:awk '/ERROR/{count[$0]++} END{for (k in count) print count[k], k}' app.log
- 思路C:sed -n '/ERROR/p' app.log | sort | uniq -c
实测下来,思路A最快,awk筛选环节占的时间比grep高出一截;思路B是单进程,但awk本身的正则匹配和字段解析开销更大;思路C因为sort参与,整体最慢。结论是:纯文本行筛选,grep永远是最优的第一选择,把更复杂的逻辑留给下游。
还有个常见的性能误区:在shell里写 while read line; do echo "$line" | grep ...; done 这种逐行循环,比如误以为比直接grep更灵活。实际上,每起一个子进程的开销是巨大的,处理大型日志的时候这种写法会让脚本慢到不可接受。能用原生awk处理的循环逻辑,就不要在shell层写循环。
6.3 中文与locale对正则的影响
中文日志的匹配是另一个容易让人困惑的点。默认情况下,GNU grep和awk的正则行为受当前locale影响。在UTF-8环境里的 [[:alpha:]] 到底匹不匹配中文,不同系统、不同版本表现不一致。生产环境为了避免不确定性,我有一个固定的习惯:在处理中文内容时,不要依赖字符类,直接在正则里写中文字面量,比如 grep '错误' 或 awk '/错误/',这些字面量的匹配结果在所有locale下都是一致的。
如果脚本里需要强制统一行为,可以在命令前显式设置环境变量:
bash复制export LC_ALL=C
LC_ALL=C 会让正则按字节处理,速度更快,但对UTF-8多字节字符的匹配会变成字节级别的匹配,原本用中文字面量的地方不会受影响,但 [[:alpha:]] 会匹配不到中文。这两者各有取舍,关键是搞清楚当前环境用的是哪个locale,而不要假设所有机器都一样。
最后再分享一个我个人的习惯:凡是超过一行的复杂管道脚本,我不会直接在终端里敲,而是会写成脚本文件,并且在每行管道后面手动加上注释,把“为什么这里用grep而不是awk”“为什么这个字段是$9而不是$7”都记录下来。理由很简单,这类文本处理命令往往是急性子,写完过两个星期再看,你会发现自己完全忘了当时的判断依据。记录下来的选型理由,能让脚本变成团队里可复用的经验文档,而不仅仅是一次性的个人操作。
