Shell 从入门到精通:保姆级教程(九)

本章系统讲解Linux文本处理核心工具:cut(按列截取)、sed(流式编辑)、awk(文本分析语言)和sort(排序),并补充grep的行级查找能力。重点涵盖各工具的基本语法、典型用法(如sed替换与模式空间、awk内置变量与数组统计、sort数值排序与去重)、常见陷阱(如空格分隔问题、中文乱码、NR/FNR区别)及多工具协同实战(日志分析、配置提取、CSV计算等),构成完整的Shell文本处理技术栈。

第 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 备份。这个操作不可逆。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值