接手 Linux 服务器时间长了,你会发现一个特别有意思的现象:很多看起来复杂得要命的问题,最后查来查去,都落到几个最基础的命令上。尤其是处理日志、清洗数据、批量改配置这种活儿,cut、sed、awk、sort这四个命令只要玩得转,效率能甩开别人好几条街。我甚至觉得,把这四个命令练熟了,比背一堆花哨的脚本框架都管用。
这篇文章不打算按部就班地讲手册,而是直接按“实际干活”的思路来拆。我会把这四个命令放在具体场景里,告诉你什么时候用哪个、它们怎么配合、哪些坑是我踩过的。不管是刚接触 Shell 的新手,还是想系统梳理一遍的老手,跟着走一遍,应该都能有收获。
1. 四个命令的分工与合作:先搞清楚谁干什么
很多人学这四个命令,是分开一个个学的,学完还是懵。我的建议是反过来:先看它们的分工,再去看语法,你会突然发现思路清晰很多。
把它们想象成一条手工流水线:cut负责“切”——按列把一行数据劈开;sed负责“改”——对文本流做替换、删除、插入;awk负责“算”——它能把文本按字段拆开,然后做统计、格式化、甚至写简单的判断逻辑;sort负责“排”——把结果按你要的顺序输出,顺便还能配合uniq去重计数。
分工清楚之后,组合起来威力就出来了。举个例子,线上 Nginx 日志想统计“哪个 IP 访问最频繁”,你大概率会这样写:
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -20这段命令里,awk负责把第一列 IP 提出来,sort负责把相同 IP 排到一起,uniq -c数出每个 IP 出现次数,再交给sort -nr按数字倒序排,最后head取前 20。整个过程没有任何一个命令是多余的,就是流水线作业。
所以我的建议是:不要孤立地记参数,而是把它们串在一条“处理链”里理解。下面逐个拆开讲,但我更希望你关注它们各自的“边界”——每个命令擅长什么、不擅长什么。
2. cut 基本功:列提取不是只会 -d -f 就完事了
cut是四个命令里最“简单粗暴”的一个。它的核心思路就是按列切,语法不复杂,但正因为简单,很多人用的时候容易卡在一些细节上。
2.1 三个核心参数:-d、-f、-c
-d指定分隔符,默认是制表符TAB-f指定取第几列(字段)-c按字符位置切,而不是按分隔符切
最常见的组合是-d配-f。比如/etc/passwd文件用冒号分隔,想提取用户名和登录 Shell:
cut -d: -f1,7 /etc/passwd这里-f1,7表示取第 1 列和第 7 列,输出出来就是“用户名”加“登录Shell”的对照表。如果要取连续的几列,可以写成-f1-3或-f1-3,5,逗号用来做集合。
-c用的场景相对少,但很合适处理固定宽度的文本。比如某些程序输出的日志,每一行的前 10 个字符是时间戳,你想单独把时间戳抠出来:
cut -c1-10 app.log这就把每行的第 1 到第 10 个字符切出来了,不需要管分隔符是什么。
2.2 cut 的两个明显局限
用cut要清楚它的短板,否则会在某些场景下浪费大量时间。
第一个局限:它默认不支持“按多个连续空格做分隔符”。比如你用ps aux查看进程,输出里各列之间是多个空格,这时候你cut -d' ' -f2大概率拿不到 PID,因为列之间空格的个数不固定。正确的做法要么先tr -s ' '压缩空格,要么直接改用awk。所以我自己用cut时,基本都是处理分隔符明确的文件,比如配置文件、CSV、/etc/passwd这类;一旦遇到多空格对齐的文本,直接换成awk更省事。
第二个局限:cut不能重排列顺序。它只能按原顺序输出你选中的列,没法像awk那样任意调整列的前后位置。想“把第三列放第一列”这种操作,cut是干不了的。
2.3 实际场景:批量提取系统用户信息
我经常在排查服务器账号的时候用这样一个组合:
cut -d: -f1,3,7 /etc/passwd | awk -F: '$2 >= 1000 {print}'先用cut把用户名、UID、Shell 提出来,再用awk过滤 UID 大于等于 1000 的普通用户。这里cut负责“提”,awk负责“筛”,分工很清晰,代码也一眼能看懂。
3. sed 进阶:不只是替换,它是一把文本手术刀
sed被称作“流编辑器”,它最大的特点是:一行一行地读入数据,处理完再输出。这种“一边读一边处理”的模式,让你可以处理超大文件,不用担心一次性载入内存的问题。
3.1 核心用法:替换、删除、打印
入门建议从最常用的三个动作开始:
s:替换d:删除p:打印
替换是最常见的使用方式。比如把配置文件里所有localhost改为127.0.0.1:
sed -i 's/localhost/127.0.0.1/g' config.ini这里-i表示原地修改,g表示替换每一行中所有匹配的地方,而不是只替换第一个。很多新手第一次用sed -i时容易紧张,生怕把文件改坏。我的建议是:先用不加-i的命令看输出,确认没问题后再加-i落地。
删除场景也很常用。比如想去掉/etc/ssh/sshd_config里的注释行和空行,方便看有效配置:
sed -e '/^#/d' -e '/^$/d' /etc/ssh/sshd_config-e表示连续执行多条 sed 指令,这里是把“以 # 开头”和“空行”都删掉。
打印模式要结合-n来理解。sed -n '5p' file.txt的意思是不输出所有行,只打印第 5 行。这有点像head -5 | tail -1,但写法更简洁,而且能指定多个行号或范围:
sed -n '10,20p' app.log这就把第 10 到第 20 行打印出来了,非常适合从日志里截取一段上下文。
3.2 定址与正则:sed 灵不灵活全靠它
sed的命令前面可以加“地址范围”,指定要处理哪些行。这个功能非常强大,我举两个典型的场景。
第一个是根据行号范围。比如你只想修改前 10 行里的某个内容:
sed -i '1,10s/foo/bar/g' test.txt第二个是根据正则匹配范围。比如你只处理包含ERROR的行:
sed -i '/ERROR/s/DEBUG/INFO/g' app.log这条的意思是:只在包含ERROR的行里,把DEBUG替换成INFO。这种“按内容定址”的方式,在处理日志时特别好用。
还有一个很经典的范围用法——从某个标记开始,到另一个标记结束:
sed -n '/BEGIN/,/END/p' data.txt这种写法适合从结构化文件里抽取片段,比如提取一段配置块。
3.3 关于 -i 的注意事项
sed -i在不同系统上行为略有差异。Linux 上直接sed -i 's/.../.../' file就行;但如果你是 macOS,建议写成sed -i '' 's/.../.../' file,因为 macOS 的sed要求-i后面必须跟一个备份后缀,如果不给后缀就必须写一个空字符串。这一点跨平台写脚本时特别容易踩坑。
另外,我习惯在做批量修改之前先备份:
cp app.conf app.conf.bak sed -i 's/old/new/g' app.conf说白了,sed -i是“原地修改”,它是在原文件上操作的。默认情况下它不是原子的,一旦中途出问题,文件可能处于半修改状态。备份是个好习惯,尤其在生产环境操作时。
4. awk 核心逻辑:它本质是一个微型编程环境
如果说sed是手术刀,awk就是一台小型加工中心。它太强了,很多人学了多年也只是用了皮毛。但我觉得,掌握它的核心模型之后,大部分日常工作场景就足够了。
4.1 awk 的工作模型:BEGIN、主循环、END
awk处理文件时,本质上分三个阶段:
BEGIN块:读文件之前执行,通常用来初始化变量、设置分隔符- 主循环:逐行读入,按你写的条件处理每一行
END块:所有行处理完之后执行,通常用来输出汇总结果
这个模型理解后,很多 awk 脚本你就看得懂了。比如我想统计一个文件的总行数和总字节数:
awk '{lines++; chars += length($0)} END {print lines, chars}' file.txt主循环里逐行累加,END里输出结果。这就是 awk 典型的“统计器”写法。
4.2 内置变量:NF、NR、FS、OFS
awk有四个内置变量,你必须了然于心:
NF:当前行的字段数NR:当前处理到第几行FS:输入字段分隔符,默认是空格OFS:输出字段分隔符,默认也是空格
配合NF可以快速筛选格式异常的行。比如一个 CSV 文件,正常每行应该有 5 个字段,但有些行多了一列,想快速找出来:
awk -F',' 'NF != 5 {print NR": " $0}' data.csv这条命令会把所有字段数不等于 5 的行打出来,并标上行号,排查脏数据非常方便。
OFS的作用体现在修改输出格式。举个例子,把/etc/passwd的冒号分隔改成逗号分隔:
awk -F: 'BEGIN{OFS=","} {print $1, $3, $7}' /etc/passwd-F:是控制输入分隔符,OFS=","是控制输出分隔符,一进一出,格式就变了。
4.3 printf 格式化:让输出变成对齐的表格
awk里的printf是格式化输出神器,特别适合生成对齐的报表。比如我想输出“用户名、UID、HOME”三列,且对齐得整整齐齐:
awk -F: 'BEGIN{printf "%-20s %-8s %-30s\n", "USER", "UID", "HOME"} {printf "%-20s %-8s %-30s\n", $1, $3, $6}' /etc/passwd这里%-20s表示左对齐并占满 20 个字符宽度,数字-8、-30同理。这样输出的内容即使数据长短不一,排版也像表格一样整齐,看配置文件、导出报告时体验特别好。
4.4 条件与统计:awk 的拿手好戏
awk 非常善于做“带条件的统计”。比如一段接口日志,想统计“状态码为 500 的请求次数”和“平均响应时间”:
awk '$9 == 500 {count++; sum += $NF} END {print count, sum/count}' access.log这里$9是状态码所在列,$NF是最后一列(假设是响应时间),awk 会自动把字符串转成数字进行运算。如果 count 为 0,sum/count会报“除以 0”错误,实际写脚本时建议加个判断,这是后话。
awk 还有一个常用技巧:按多个条件分组统计。比如按状态码分组,统计每个状态码的出现次数:
awk '{codes[$9]++} END {for (code in codes) print code, codes[code]}' access.log这里用了一个关联数组codes,下标是状态码,值是次数。END循环遍历打印。这个模式可以扩展到任意维度,比如按访问路径、按 IP 段、按小时统计等等。
5. sort 不只是排序:参数选不对,结果差很远
sort看起来最简单,实际上坑最多。很多“排序结果不对”的线上问题,往往不是数据有问题,而是参数用错了。
5.1 必须掌握的五个参数
-n:按数字排序,不是按字典序-r:倒序-t:指定分隔符-k:指定按第几列排序-u:去重
这里重点说一说-n。用sort file.txt默认是按字典序排的,如果你文件里是一堆数字,比如10, 100, 2, 25,按字典序排出来是10, 100, 2, 25,这显然不符合直觉。想按数值大小排,必须加-n:
sort -n numbers.txt这件事看似基础,但在脚本里查 Top 日志时特别容易忘记。比如sort -k3 file和sort -k3n file结果是完全不同的,前者按第 3 列字典序排,后者按数值排。
5.2 按指定列排序:-t 和 -k 组合
-t和-k一般是配套使用的。比如文件内容是这样的:
alice 90 bob 75 carol 88想按第二列的分数从高到低排:
sort -t' ' -k2 -nr scores.txt注意这里-t指定分隔符为空格,-k2指定按第二列排序,-nr一起写表示“按数字倒序”。输出结果就是分数高的排前面。
-k还能指定多级排序规则。比如想先按第一列排,再按第二列排:
sort -t',' -k1,1 -k2,2n data.csv这种写法在生产环境处理表格数据时非常实用。
5.3 sort 与 uniq 配合去重计数
sort和uniq是天然搭档,原因在于uniq只能处理“相邻的重复行”,所以去重前必须先排序,让相同的行聚在一起。
最常见的场景是统计日志里的 IP 访问次数:
sort access.log | uniq -c这会把每一行去重,并在每行前面加上出现次数。如果要按访问次数排序,再加一层sort -nr就行。
特别提醒一个细节:uniq -c输出的第一列是“次数”,所以如果你想按次数倒序取 Top 10,对应的命令是:
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10这里的第二次sort -nr就是针对uniq -c的结果做数值倒序排序。
6. 综合实战:用四个命令完成日志分析与报告输出
前面把四个命令分开讲了,但真正干活的时候,它们往往是一条流水线。下面我用一个完整的实战场景来演示怎么串联。
6.1 场景说明
假设你维护一个 Web 服务,日志文件access.log每行的格式是:
192.168.1.10 - - [12/Oct/2024:08:30:15 +0800] "GET /api/user/list HTTP/1.1" 200 5321 0.045 192.168.1.21 - - [12/Oct/2024:08:31:02 +0800] "POST /api/login HTTP/1.1" 500 1024 0.302 ...你接到一个任务:写一条命令,统计出当天访问量排前 10 的接口路径,并输出它们的访问次数和平均响应时间。
6.2 第一步:用 awk 提取字段并格式化输出
首先,每个日志行的请求行在$7(假设以空格分隔),状态码在$9,响应时间在$NF。我想把“路径、状态码、响应时间”三个字段提出来,供后续处理:
awk '{print $7, $9, $NF}' access.log这一步输出大概长这样:
/api/user/list 200 0.045 /api/login 500 0.302 ...6.3 第二步:用 sort 和 uniq 统计接口访问次数
提取路径列,排序后去重计数:
awk '{print $7}' access.log | sort | uniq -c如果想按访问次数倒序,再补一个sort -nr:
awk '{print $7}' access.log | sort | uniq -c | sort -nr | head -10这就能看到访问量最大的 10 个接口路径。
6.4 第三步:用 awk 追加平均响应时间
只统计次数还不够,还想算平均响应时间。这一步适合重新回归awk,因为要同时做“分组”和“累加”:
awk '{count[$7]++; sum[$7] += $NF} END {for (path in count) printf "%-30s %d %0.3f\n", path, count[path], sum[path]/count[path]}' access.log | sort -nr -k3 | head -10这里用两个关联数组count和sum分别记录次数和总耗时,最后在END里用printf输出对齐的表格。最后一个sort -nr -k3是按第三列(平均耗时)倒序排,也可以改成按第二列(访问次数)排,取决于你想看什么维度的 Top。
6.5 第四步:用 sed 做简单过滤或脱敏
假设只想统计状态码为 200 的请求,可以在流水线开头加一层过滤:
sed -n '/ 200 /p' access.log | awk '{count[$7]++; sum[$7] += $NF} ...'或直接在 awk 里通过条件判断过滤,都是可以的。这里用 sed 的好处是,如果后面还要做别的文本级替换,可以顺便一起处理。
四条命令依次出场,各干各的活,最后拼出一个结果。这种思路比写一个 200 行的 Python 脚本要轻快很多,而且执行效率极高。
7. 常见问题与排查技巧实录
平时工作中,四个命令的报错或“意外结果”主要集中在几个点上。我把常见的坑整理成一张速查表,都是实测过的:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
cut -d' '提取多空格文本列错乱 | 分隔符是连续空格,cut 不支持 | 改用awk '{print $N}'或先tr -s ' ' |
sort结果数字顺序不对 | 没加-n,按字典序排 | 加-n,如sort -nr |
sed -i在 mac 上报错或备份文件多出后缀 | 苹果系统 sed 语法差异 | 用sed -i '' 's/.../.../' file |
| awk 统计时提示除以 0 | 分组里没有匹配行,分母为 0 | 加if (count[path] > 0)判断 |
| awk 输出列顺序不对 | 忘记设置OFS | 在BEGIN块里显式指定OFS |
uniq -c去重失败,次数全是 1 | 数据没有先排序 | 先sort再uniq -c |
多级排序时-k写错导致顺序错乱 | -k2默认会排序到行尾 | 用-k2,2明确“只排第二列” |
用sed处理含/的路径很别扭 | /是 sed 的默认分隔符 | 改用#或 ` |
7.1 sed 替换中包含斜杠怎么办
这是新手问得最多的一个问题。比如想替换路径/usr/local为/opt,直接写s//usr/local//opt/g会报错。实际上 sed 的分隔符可以换,常见做法是用#或|:
sed -i 's#/usr/local#/opt#g' config.sh我一般喜欢用#做路径替换的分隔符,读起来清爽,也不用担心转义问题。
7.2 awk 取最后一列:$NF 的妙用
awk 里$NF表示当前行的最后一个字段,而NF本身是字段总数。处理长度不定的日志行时,用$NF取最后一个字段非常方便,比如取响应时间、取末尾的耗时等。如果你的行尾有空格,先awk '{$1=$1; print}'重新规范化一下,$NF才会准确。
7.3 常见问题:sort 还是 uniq 的坑
uniq检测的是“相邻行是否相同”,而不是“全文件是否相同”。所以如果你直接cat file | uniq -c,很可能发现明明有重复项,但统计次数不对。正确的姿势永远是先sort:
sort file.txt | uniq -c如果文件很大,也可以让sort使用足够的内存来优化性能,必要时用-S 1G指定排序缓冲区大小,避免磁盘 I/O 拖慢速度。
7.4 我的排错习惯
遇到这四个命令组合出来的结果不对,我一般按这个顺序排查:
- 先单独跑每一步,看中间输出是否符合预期
- 检查分隔符是什么:空格、制表符还是连续空格
- 检查有没有加
-n,数字排序是不是按字典序了 - 检查
-k指定的列号是否符合实际字段位置 - 如果是 awk 统计,加一个临时
{print NR, NF, $0}看行解析是否正确
只要中间任一步的输出和预期不一致,就直接定位问题,而不是盯着整条长命令发呆。
8. 写在最后的经验之谈
四个命令单独看,每个都不难;难的是把它们“组合”成解决问题的思维习惯。我见过不少同事,一遇到文本处理就想着装 Python、写脚本,其实很多任务用 Shell 一行就能完成,而且执行效率和数据量级都够用。关键在于:先想清楚输入是什么、输出要什么、中间需要做哪几步变换,然后再去挑命令。想明白这一步,比背任何参数都重要。
还有一点:写脚本之前先在命令行手动跑一遍,确认每个环节的输出都正确,再把命令固化到脚本里。这样能避免很多“脚本一执行就报错,还不知道错在哪”的尴尬。特别是sed -i和管道组合的时候,先在测试目录里跑一次,永远是最稳妥的做法。
如果你能在实际工作中把这条“提取—处理—汇总—排序”的链路练成直觉,再去学别的命令或者更复杂的脚本语言,都会轻松很多。这四件套对我来说,就是 Linux 文本处理的基本盘。