第 9 章 文本四剑客:cut、sed、awk、sort
Shell 真正的主场是处理文本。日志、配置、CSV、命令输出,全都是文本。这一章的四件工具,加上老朋友 grep,构成了 Linux 文本处理的完整武器库。
先给一张选型表,后面逐个展开:
| 工具 | 一句话定位 | 典型场景 |
|---|---|---|
| grep | 按行查找 | 找包含 error 的行 |
| cut | 按列截取 | 取第 3 列、取前 5 个字符 |
| sed | 按行增删改查 | 批量替换、删除空行、插入行 |
| awk | 按列分析 | 求和、统计、格式化报表 |
| sort | 排序 | 按第 2 列数字降序、去重 |
9.1 cut:按列切
cut [options] 文件名
| 选项 | 作用 |
|---|---|
| -d 分隔符 | 指定分隔符,默认是制表符 |
| -f 范围 | 取第几列(field) |
| -c 范围 | 按字符取 |
| -b 范围 | 按字节取 |
| -n | 配合 -b,不切断多字节字符 |
范围写法(四种):
| 写法 | 含义 |
|---|---|
| n- | 第 n 列到最后一列 |
| n-m | 第 n 列到第 m 列 |
| -m | 第 1 列到第 m 列 |
| n1,n2 | 指定多列(枚举) |
数据准备:
cat > cut1.txt << 'EOF'
AA itheima 11 XX
BB itcast 22 XXX
CC Shell 33 XXXX
DD it 44 XXXXXXX
EOF
⚠️ 这里用单个空格分隔,cut 才取得到正确的列。如果字段之间是连续多个空格或制表符,
cut -d " "会得到一串空列——这种数据请改用 awk(它能自动合并连续空白)。
示例 1:取第 1 列
cut cut1.txt -d " " -f 1
输出:
AA
BB
CC
DD
示例 2:取第 1、3 列(枚举)
cut cut1.txt -d " " -f 1,3
输出:
AA 11
BB 22
CC 33
DD 44
示例 3:取第 2 列到末尾
cut cut1.txt -d " " -f 2-
示例 4:取前 2 列
cut -d " " -f -2 cut1.txt
示例 5:按字符取
cut cut1.txt -c1-3 # 每行前 3 个字符
cut cut1.txt -c 4- # 第 4 个字符起到末尾
cut cut1.txt -c -3 # 前 3 个字符
示例 6:按字节与中文
echo "abc传智播客" | cut -b -3 # abc
echo "abc传智播客" | cut -b -6 # abc传 (一个汉字 3 字节,刚好切出"传")
echo "abc传智播客" | cut -nb -4 # abc传 (-n 避免切断多字节字符)
💡 结论:处理中文用
-c或-nb,不要用-b,否则会出现乱码。
实战:提取 bash 进程 PID
ps -aux | grep 'bash' | head -n 1 | cut -d " " -f 8
实战:提取 IP
ifconfig | grep broadcast | cut -d " " -f 10
⚠️ 这两条命令对"列的位置"极度敏感,字段一变就失效。更稳的写法是用 awk(见 9.3)。
9.2 sed:流编辑器
sed(stream editor)逐行读入、处理、输出,适合批量、非交互地修改文本。vim 是交互式编辑,sed 是"一条命令改一万个文件"。
工作原理:
读一行 → 放入模式空间 → 按脚本处理 → 输出到屏幕 → 读下一行
语法:
sed [选项] '地址+命令' 文件名
| 选项 | 作用 |
|---|---|
| -n | 取消默认输出,只输出处理过的行(常配 p) |
| -i | 直接修改源文件(不加则只预览) |
| -e | 指定多条命令 |
| -r / -E | 使用扩展正则 |
常用命令:
| 命令 | 作用 |
|---|---|
| a | 在匹配行后面追加 |
| i | 在匹配行前面插入 |
| c | 替换整行 |
| d | 删除行 |
| p | 打印行(配 -n) |
| s/旧/新/ | 替换字符串 |
| = | 打印行号 |
数据准备:
cat > sed.txt << 'EOF'
ABC
itheima itheima
itcast
123
itheima
EOF
9.2.1 增加
sed '3ahello' sed.txt # 第 3 行后追加 hello
sed '3ihello' sed.txt # 第 3 行前插入 hello
sed '$ahello' sed.txt # 最后一行后追加
sed '/itheima/ahello' sed.txt # 每个匹配 itheima 的行后面都加
示例(sed '3ahello'):
ABC
itheima itheima
itcast
hello
123
itheima
⚠️ 不加
-i不会改源文件,只是打印处理后的结果。这是新手最常见的误解。
9.2.2 删除
sed '2d' sed.txt # 删第 2 行
sed '1d;4d' sed.txt # 删第 1、4 行
sed '1~2d' sed.txt # 从第 1 行起每隔 2 行删一行(删奇数行)
sed '1,3d' sed.txt # 删 1-3 行
sed '1,3!d' sed.txt # 删除 1-3 行以外的行(取反)
sed '$d' sed.txt # 删最后一行
sed '/itheima/d' sed.txt # 删所有匹配 itheima 的行
sed '/itheima/,$d' sed.txt # 从匹配行删到最后一行
sed '/itheima/,+1d' sed.txt # 删匹配行及其后一行
sed '/itheima\|itcast/!d' sed.txt # 只保留匹配 itheima 或 itcast 的行
sed '/^$/d' sed.txt # 删空行
💡
1~2是 GNU sed 的"步长"语法:从第 1 行开始,每隔 2 行匹配一次。删偶数行就是0~2d或2~2d。
9.2.3 修改
sed '1chello' sed.txt # 第 1 行整行替换为 hello
sed '/itheima/chello' sed.txt # 匹配行整行替换
sed '$chello' sed.txt # 最后一行整行替换
9.2.4 替换(最常用)
sed 's/itheima/hello/' sed.txt # 每行只替换第一个
sed 's/itheima/hello/g' sed.txt # 全局替换
sed 's/itheima/hello/2' sed.txt # 每行只替换第 2 个匹配
sed -i 's/itheima/hello/g' sed.txt # 直接改文件
⚠️
s/旧/新/末尾的/不能少,少了会报unterminated s command。
进阶替换技巧:
sed 's/^/#/' sed.txt # 每行行首加 #
sed 's/$/ test/' sed.txt # 每行行尾加 " test"
sed 's/[0-9]//g' sed.txt # 删除所有数字
sed 's/ */ /g' sed.txt # 多个空格压成一个
sed 's/\(.*\):\(.*\)/\2:\1/' file # 交换冒号两侧(分组引用)
sed -E 's/([0-9]+)px/\1pt/g' f # 扩展正则,px 转 pt
& 表示"整个匹配内容",常用于包裹:
sed 's/[0-9]\+/[&]/g' sed.txt # 给每个数字加方括号
9.2.5 查询
sed -n '/itcast/p' sed.txt # 打印含 itcast 的行
sed -n '2,4p' sed.txt # 打印 2-4 行
sed -n '$=' sed.txt # 打印总行数
ps -aux | sed -n '/sshd/p' # 管道过滤
9.2.6 多条命令组合
sed -e '1d' -e 's/itheima/itcast/g' sed.txt
sed '1d;s/itheima/itcast/g' sed.txt # 分号写法等价
9.2.7 高级:模式空间与暂存空间
sed 有两个缓冲区:
- 模式空间(pattern space):当前处理的那一行;
- 暂存空间(hold space):一块草稿纸,初始为空。
| 命令 | 作用 |
|---|---|
| h | 模式空间 → 暂存空间(覆盖) |
| H | 模式空间 → 暂存空间(追加) |
| g | 暂存空间 → 模式空间(覆盖) |
| G | 暂存空间 → 模式空间(追加) |
| x | 交换两个空间 |
经典用法:
sed '1h;$G' sed.txt # 把第一行复制到最后一行
sed '1{h;d};$G' sed.txt # 把第一行剪切到最后一行
sed '1h;2,$g' sed.txt # 用第一行覆盖第 2 行到最后一行
sed '1,3H;$G' sed.txt # 把前 3 行追加到最后
sed 'G' sed.txt # 每行后面插一个空行
例如 sed '1h;$G' sed.txt 输出:
ABC
itheima itheima
itcast
123
itheima
ABC
G 单独用(不带地址)就是"每行后面加一个空行",这是给文本加空行的标准做法。
⚠️
-i直接改文件前,先不加-i预览一遍。sed 的删除和替换没有回收站。
9.3 awk:文本分析语言
awk 不只是命令,它是一门小型编程语言:有变量、数组、函数、流程控制。它把每行按分隔符切成字段,然后对字段做各种加工。
名字来自三位创始人姓氏首字母:Aho、Weinberger、Kernighan。
语法:
awk [选项] 'pattern {action}' 文件
- pattern:匹配条件(正则、比较、BEGIN、END),省略则每行都处理;
- action:要执行的操作,通常是 print;
- -F:指定分隔符;
- -v:传入自定义变量。
内置变量(必须记住):
| 变量 | 含义 |
|---|---|
| $0 | 整行内容 |
| $1 $2 ... | 第 1、2... 个字段 |
| $NF | 最后一个字段 |
| $(NF-1) | 倒数第二个字段 |
| NF | 当前行的字段数 |
| NR | 当前行号(跨文件累计) |
| FNR | 当前文件内的行号 |
| FILENAME | 当前文件名 |
| FS | 输入分隔符(等价 -F) |
| OFS | 输出分隔符(默认空格) |
| RS | 记录分隔符(默认换行) |
数据准备:
cp /etc/passwd ./
示例 1:默认按空格切
echo "abc 123 456" | awk '{print $1"&"$2"&"$3}'
输出:
abc&123&456
示例 2:打印匹配行
awk '/root/' passwd
输出(示例):
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
只写 pattern 不写 action 时,默认动作是 print $0。
示例 3:指定分隔符并取列
awk -F: '/root/{print $7}' passwd
输出:
/bin/bash
/sbin/nologin
示例 4:输出行号、列数、整行
awk -F: '{print "文件名:" FILENAME ", 行号:" NR ", 列数:" NF ", 内容:" $0}' passwd
输出(节选):
文件名:passwd, 行号:1, 列数:7, 内容:root:x:0:0:root:/root:/bin/bash
文件名:passwd, 行号:2, 列数:7, 内容:bin:x:1:1:bin:/bin:/sbin/nologin
示例 5:printf 格式化输出
awk -F: '{printf "%-15s %5s %5s\n", $1, NR, NF}' passwd | head -3
输出:
root 1 7
bin 2 7
daemon 3 7
printf 格式说明:
| 格式 | 含义 |
|---|---|
| %s | 字符串 |
| %5s | 宽度 5,右对齐 |
| %-5s | 宽度 5,左对齐 |
| %d / %5d | 整数 / 宽度 5 |
| %.2f | 保留 2 位小数 |
| \n \t | 换行 / 制表符 |
⚠️ printf 不会自动换行,必须手写
\n;print 会自动换行。
示例 6:按行号取
awk -F: 'NR==2{print $0}' passwd # 第 2 行
awk -F: 'NR>=10 && NR<=12{print $1}' passwd # 10-12 行的第 1 列
awk 'NR%2==1' file # 奇数行
示例 7:多分隔符
echo "one:two/three" | awk -F '[:/]' '{print $1"\n"$2"\n"$3}'
输出:
one
two
three
-F '[:/]' 是正则字符组,表示"冒号或斜杠"都算分隔符。
示例 8:BEGIN 与 END
echo -e "abc\nabc" | awk 'BEGIN{print "开始..."} {print $0} END{print "结束..."}'
输出:
开始...
abc
abc
结束...
BEGIN{...}:读第一行之前执行一次,适合打印表头、初始化变量;END{...}:所有行读完后执行一次,适合汇总、求平均。
示例 9:求和(最经典的 END 用法)
cat > score.txt << 'EOF'
张三 40
李四 50
王五 60
EOF
awk '{sum+=$2} END{print "总分: "sum}' score.txt
输出:
总分: 150
示例 10:条件判断与循环
# 输出第 2 列大于 45 的行
awk '$2>45 {print $1, $2}' score.txt
求平均分
awk '{sum+=$2; n++} END{printf "平均分: %.2f\n", sum/n}' score.txt
循环拼接(连续空格、制表符都算分隔符)
echo "abc itheima itcast 21" | awk -v s="" '{for(i=1;i<=NF;i++) s=s$i; print s}'
输出:
李四 50
王五 60
平均分: 50.00
abctheimaitcast21
💡
-v s=""用来在 awk 内部初始化一个变量。awk 的变量默认是空的,但显式声明可读性更好;-v传进去的变量在 BEGIN 里也能用。
示例 11:数组统计(词频)
echo "a b a c b a" | awk '{for(i=1;i<=NF;i++) c[$i]++} END{for(k in c) print c[k], k}'
输出:
3 a
2 b
1 c
这是 awk 最强大的地方:内置关联数组,天然适合做统计。
示例 12:多文件与 FNR
awk '{print FILENAME, FNR, $0}' a.txt b.txt
NR 会跨文件累加,FNR 每个文件从 1 重新开始——处理多文件时用 FNR 才不会错。
9.4 sort:排序
sort [选项] 文件
| 选项 | 作用 |
|---|---|
| -n | 按数值排序(默认按字符串) |
| -r | 降序 |
| -t 分隔符 | 指定字段分隔符 |
| -k start[,end] | 指定排序的列 |
| -u | 去重(配合 -k 表示按指定列去重) |
| -o 文件 | 结果写入文件(可直接覆盖源文件) |
| -f | 忽略大小写 |
| -b | 忽略行首空格 |
| -h | 人类可读的数字排序(1K、2M) |
数据准备:
cat > sort.txt << 'EOF'
张三 30
李四 95
播仔 85
播仔 85
播仔 86
AA 85
播妞 100
EOF
示例 1:按第 2 列数字升序
sort -t " " -k2n,2 sort.txt
输出:
张三 30
播仔 85
播仔 85
AA 85
播仔 86
李四 95
播妞 100
-k2n,2 的含义:从第 2 列开始到第 2 列结束,按数值(n)排序。
⚠️ 不写
-n就是字符串排序,100 会排在 30 前面(因为 '1' < '3')。这是最经典的排序坑。
示例 2:排序 + 去重
sort -t " " -k2n,2 -uk1,2 sort.txt
-uk1,2 表示"按第 1、2 列组合去重",因为已经按第 2 列排好序,重复行会相邻,-u 就能去掉。
💡 先排序再去重是 uniq 类操作的前提,顺序反了去不干净。
示例 3:降序
sort -t " " -k2nr,2 sort.txt
示例 4:结果存文件
sort -t " " -k2n,2 -o sort2.txt sort.txt
⚠️ 想覆盖源文件必须用
-o,不能写sort file > file(重定向会先把文件清空)。
示例 5:多列排序
cat > sort3.txt << 'EOF'
公司A,部门A,3
公司A,部门B,0
公司B,部门A,30
公司B,部门B,40
公司C,部门A,30
公司C,部门C,100
EOF
sort -t "," -k1,1 -k3nr,3 sort3.txt
输出:
公司A,部门A,3
公司A,部门B,0
公司B,部门B,40
公司B,部门A,30
公司C,部门C,100
公司C,部门A,30
先按第 1 列字符串升序,第 1 列相同的再按第 3 列数字降序。
9.5 grep:按行查找
grep [选项] '模式' 文件
| 选项 | 作用 |
|---|---|
| -i | 忽略大小写 |
| -v | 反向匹配(不含关键字的行) |
| -n | 显示行号 |
| -c | 只输出匹配行数 |
| -l | 只输出文件名 |
| -r | 递归搜索目录 |
| -w | 全词匹配 |
| -E | 扩展正则(等价 egrep) |
| -q | 静默,只用退出状态 |
| -A n / -B n / -C n | 显示后 n 行 / 前 n 行 / 前后 n 行 |
grep -n "root" /etc/passwd
grep -vc "^#" /etc/nginx/nginx.conf # 非注释行数
grep -rn "timeout" /etc/nginx/ # 递归 + 行号
grep -E "error|warn" app.log # 扩展正则
grep -q "root" /etc/passwd && echo yes # 只判存在性
9.6 四剑客怎么配合
场景 1:统计访问日志 Top 10 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
场景 2:找出 nginx 配置里所有 listen 端口
grep -rh "listen" /etc/nginx/ | awk '{print $2}' | tr -d ';' | sort -un
场景 3:把 CSV 的第 2 列求和
awk -F, 'NR>1{sum+=$2} END{print sum}' data.csv
场景 4:批量替换目录下所有 .conf 里的旧域名
grep -rl "old.example.com" /etc/app/ | xargs sed -i 's/old\.example\.com/new.example.com/g'
⚠️
sed -i批量改文件前先grep -rl确认清单,或者先cp -a备份。这个操作不可逆。
本章系统讲解Linux文本处理核心工具:cut(按列截取)、sed(流式编辑)、awk(文本分析语言)和sort(排序),并补充grep的行级查找能力。重点涵盖各工具的基本语法、典型用法(如sed替换与模式空间、awk内置变量与数组统计、sort数值排序与去重)、常见陷阱(如空格分隔问题、中文乱码、NR/FNR区别)及多工具协同实战(日志分析、配置提取、CSV计算等),构成完整的Shell文本处理技术栈。

993

被折叠的 条评论
为什么被折叠?



