Linux: 文本处理三剑客——awk / sed / cut
如果说 grep 是"找",那 awk、sed、cut 就是"改"。cut 切列、awk 做数据报告、sed 做批量替换——三者配合管道,能处理几乎所有文本任务。
📋 前置知识:需要先掌握以下内容
- 第14课:文本搜索
1. 你将学到
- cut 按分隔符截取列
- awk 的高级列处理
- sed 替换与删除
- tr 字符转换
- diff 文件对比
2. 一个 CSV 文件处理的故事
(1) 痛点:CSV 文件需要转换格式
小明拿到一个 CSV 文件,需要提取第 2 列和第 5 列,删除空行,把分隔符从逗号改为制表符。
(2) 一条管道链搞定
BASH
cat data.csv | cut -d',' -f2,5 | sed '/^$/d' | tr ',' '\t' > output.tsv
cut -d',' -f2,5:按逗号切,取第 2 和第 5 列sed '/^$/d':删除空行tr ',' '\t':逗号替换为制表符> output.tsv:输出为新文件
(3) 收益:数据处理无需编程
小明省去了打开 Excel、写 Python 脚本的麻烦。一行命令,数据处理完成。
3. 知识点讲解
(1) cut——按列切割
BASH
# 按分隔符切
cut -d',' -f1,3 file.csv # 以逗号分隔,取第 1、3 列
cut -d: -f1 /etc/passwd # 以冒号分隔,取用户名
> ℹ️ **说明**:`cut` 适合简单的列提取(固定分隔符、固定列号),但对于需要条件筛选、计算、格式化的场景,应该用 `awk`。例如 `awk -F: '$3 >= 1000 {print $1}' /etc/passwd` 能筛选 UID≥1000 的用户,`cut` 做不到。
# 按字符位置切
cut -c1-5 file.txt # 取第 1-5 个字符
cut -c1,3,5 file.txt # 取第 1、3、5 个字符
(2) awk——数据报告生成器
awk 是一个完整的文本处理语言。它的核心模式是:
BASH
awk '模式 {动作}' 文件
awk 内置变量:
| 变量 | 含义 |
|---|---|
$0 |
整行 |
$1 |
第 1 列 |
$2 |
第 2 列 |
NF |
列数(字段数) |
NR |
行号 |
FS |
输入列分隔符(默认空格/制表符) |
OFS |
输出列分隔符(默认空格) |
💡 提示:awk 的核心变量要记牢——
$0 是整行、$1/$2/$3 是第 1/2/3 列、NR 是行号、NF 是列数($NF 即最后一列)。配合 -F: 指定分隔符,就能处理绝大多数文本提取任务。
BASH
# 基础用法
awk '{print $1}' file.txt # 打印第 1 列
awk '{print $1, $3}' file.txt # 打印第 1、3 列
awk '{print NR, $0}' file.txt # 打印行号和整行
awk '{print $NF}' file.txt # 打印最后一列
awk -F: '{print $1}' /etc/passwd # 指定分隔符为 :
# 模式匹配
awk '/error/ {print}' log.txt # 只处理包含 error 的行
awk '$3 > 100 {print $1, $3}' data.txt # 第 3 列 > 100 才输出
# 统计运算
awk '{sum += $1} END {print sum}' numbers.txt # 求和
awk '{count++} END {print count}' file.txt # 计数
(3) sed——流编辑器
BASH
# 替换
sed 's/old/new/' file.txt # 每行第一个 old → new
sed 's/old/new/g' file.txt # 全局替换
sed 's/old/new/2' file.txt # 只替换每行第 2 个
sed 's/old/new/g' file.txt > new.txt # 输出到新文件
sed -i 's/old/new/g' file.txt # 原地编辑(直接修改文件)
sed -i.bak 's/old/new/g' file.txt # 备份原文件为 file.txt.bak
> 💡 **提示**:`sed -i` 会原地修改文件,没有撤销机会。建议先用 `sed 's/old/new/g' file.txt` 不加 `-i` 预览替换结果,确认无误后再加 `-i`。或者使用 `sed -i.bak` 自动创建备份文件。
# 删除
sed '/pattern/d' file.txt # 删除匹配 pattern 的行
sed '3d' file.txt # 删除第 3 行
sed '5,10d' file.txt # 删除第 5-10 行
sed '/^$/d' file.txt # 删除空行
# 打印
sed -n '5,10p' file.txt # 只打印第 5-10 行
sed -n '/pattern/p' file.txt # 只打印匹配行
(4) tr——字符转换
BASH
# 大小写转换
echo "hello" | tr 'a-z' 'A-Z' # HELLO
cat file.txt | tr '[:lower:]' '[:upper:]'
# 替换和删除
echo "a,b,c" | tr ',' ' ' # a b c
echo "hello world" | tr -s ' ' # hello world(压缩重复空格)
echo "hello123" | tr -d '0-9' # hello(删除数字)
echo "abc123" | tr -d '[:digit:]' # abc(删除数字)
# 换行符转换(Windows ↔ Unix)
tr -d '\r' < win.txt > unix.txt # 删除 Windows 换行符的 \r
tr '\n' ',' < list.txt # 将换行替换为逗号
(5) diff——文件对比
TEXT
📖 仅展示
diff file1.txt file2.txt # 基本对比
diff -u file1.txt file2.txt # unified 格式(更易读)
diff -i file1.txt file2.txt # 忽略大小写
diff -w file1.txt file2.txt # 忽略空白差异
diff -r dir1/ dir2/ # 递归对比目录
▶ 示例:cut 按列切割
BASH
# 提取 /etc/passwd 的用户名和 Shell
cut -d: -f1,7 /etc/passwd | head -5
# root:/bin/bash
# daemon:/usr/sbin/nologin
# bin:/usr/sbin/nologin
# 取 ls -l 输出的第 5 列(文件大小)
ls -la | tr -s ' ' | cut -d' ' -f5 | head -5
输出:
TEXT 📖 仅展示root:/bin/bash daemon:/usr/sbin/nologin bin:/usr/sbin/nologin sys:/usr/sbin/nologin sync:/usr/sbin/nologin 4096 4096 1234 5678 910
▶ 示例:awk 列处理
BASH
# 打印用户名和家目录
awk -F: '{print $1, "家目录:", $6}' /etc/passwd | head -5
# 查找 UID 大于 1000 的普通用户
awk -F: '$3 >= 1000 {print $1, "(UID:" $3 ")"}' /etc/passwd
# 计算文件总大小
ls -la | grep "^-" | awk '{sum += $5} END {print "总大小:", sum/1024/1024, "MB"}'
# 格式化输出(printf)
ls -la | awk '{printf "%-20s %8s\n", $9, $5}'
输出:
TEXT 📖 仅展示root 家目录: /root daemon 家目录: /usr/sbin bin 家目录: /bin sys 家目录: /dev nobody 家目录: /nonexistent alice (UID:1000) bob (UID:1001) 总大小: 256.5 MB .bashrc 3771 .profile 220 .bash_history 1234
▶ 示例:sed 批量替换
BASH
# 将文件中的 localhost 替换为 127.0.0.1
sed -i 's/localhost/127.0.0.1/g' config.txt
# 注释掉配置文件中 listen 开头的行
sed -i '/^listen/s/^/#/' nginx.conf
# 在文件第 2 行后插入新行
sed '2a\新插入的行' file.txt
# 提取日志中的时间戳
sed -n 's/.*\[\(.*\)\].*/\1/p' access.log | head -5
输出:
TEXT 📖 仅展示07/Jul/2026:10:15:30 +0000 07/Jul/2026:10:15:31 +0000 07/Jul/2026:10:15:32 +0000 07/Jul/2026:10:15:33 +0000 07/Jul/2026:10:15:34 +0000
▶ 示例:tr 字符转换
BASH
# 统计文本中的单词数
cat file.txt | tr -s ' ' '\n' | sort | uniq -c | sort -rn | head -10
# 将列表转换为一行(逗号分隔)
cat list.txt | tr '\n' ',' | sed 's/,$//'
# 删除配置文件中的所有注释和空行
grep -v "^#" config.txt | grep -v "^$" | tr -s '\n'
输出:
TEXT 📖 仅展示42 the 28 and 25 linux 20 file 18 command 15 user 12 system 10 process 8 directory 6 shell apple,banana,cherry,date ServerName localhost Listen 80 DocumentRoot /var/www/html ErrorLog /var/log/apache2/error.log
▶ 示例:diff 对比文件
TEXT
📖 仅展示
# 创建两个版本
echo -e "apple\nbanana\norange" > v1.txt
echo -e "apple\nblueberry\norange" > v2.txt
# 对比
diff -u v1.txt v2.txt
# --- v1.txt
# +++ v2.txt
# @@ -1,3 +1,3 @@
# apple
# -banana
# +blueberry
# orange
▶ 综合示例:系统监控报告
BASH
#!/bin/bash
# sys-report.sh - 生成系统监控报告
echo "========================================="
echo " 系统监控报告"
echo " 生成时间: $(date)"
echo "========================================="
echo ""
echo "1. CPU 负载最高的 5 个进程"
ps aux --sort=-%cpu | head -6 | awk '{printf "%-10s %-5s %-5s %s\n", $1, $2, $3, $11}'
echo ""
echo "2. 磁盘使用率"
df -h | grep "^/" | awk '{printf "%-20s %5s %5s %5s\n", $6, $2, $3, $5}'
echo ""
echo "3. 内存占用前 5"
ps aux --sort=-%mem | head -6 | awk '{printf "%-10s %-5s %-5s %s\n", $1, $2, $4, $11}'
echo ""
echo "4. /var/log 中错误数量"
grep -ric "error" /var/log/*.log 2>/dev/null | grep -v ":0" | head -10
echo ""
echo "5. 监听端口"
ss -tlnp | tail -n +2 | awk '{print $4, $1}' | sed 's/.*://'
echo ""
echo "========================================="
❓ 常见问题
Q: awk 比 cut 强在哪? A: cut 只能做简单的列切割(固定分隔符),awk 能做条件筛选、计算、格式化输出。awk 是完整的编程语言——支持变量、数组、循环、函数。简单场景用 cut,复杂场景用 awk。
Q: sed
-i修改原文件有风险吗? A: \1
Q: diff 和 vimdiff 有什么区别? A: diff 是命令行工具,输出文本对比结果。vimdiff 是 vim 的图形化对比模式,左右分屏显示,可以交互式编辑。日常简单对比用 diff,复杂对比用 vimdiff。
Q: tr 能处理中文吗? A: 可以,要小心。中文字符是 UTF-8 多字节编码,tr 按字节处理,可能会把中文字符拆开。处理中文推荐用
sed或 Python。
Q: 多个文本处理命令怎么组合效率最高? A: 原则是"一次遍历,多次处理"。把简单的过滤(grep)放在最前面减少数据量,把重的处理放在后面。避免
cat file | grep | awk | sed | ...中间产生太多临时管道。
📖 小节
cut -d',' -f1,3按分隔符切列awk '{print $1}'取列,-F:指定分隔符,/pattern/模式匹配,END最后执行sed 's/old/new/g'全局替换,-i原地编辑,/^$/d删除空行tr 'a-z' 'A-Z'大小写转换,-d删除,-s压缩重复diff -u file1 file2对比文件差异
📝 作业
- 基础题(难度⭐):用 cut 提取
/etc/passwd的用户名、UID(第 3 列)和登录 Shell,然后用 tr 将/etc/hostname的内容转为大写 - 进阶题(难度⭐⭐):用 awk 从
ls -l中过滤出文件大小大于 1MB 的文件,然后用 sed 批量替换配置文件中的端口号(8080 → 9090),备份原文件 - 挑战题(难度⭐⭐⭐):创建两个略有不同的配置文件,用
diff -u查看差异,然后写一个管道链从日志文件中提取所有错误行、按小时统计错误数量、输出 Top 5 高峰时段