Linux: 文本处理三剑客——awk / sed / cut

如果说 grep 是"找",那 awk、sed、cut 就是"改"。cut 切列、awk 做数据报告、sed 做批量替换——三者配合管道,能处理几乎所有文本任务。

📋 前置知识:需要先掌握以下内容

1. 你将学到


2. 一个 CSV 文件处理的故事

(1) 痛点:CSV 文件需要转换格式

小明拿到一个 CSV 文件,需要提取第 2 列和第 5 列,删除空行,把分隔符从逗号改为制表符。

(2) 一条管道链搞定

BASH
cat data.csv | cut -d',' -f2,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 | ... 中间产生太多临时管道。


📖 小节


📝 作业

  1. 基础题(难度⭐):用 cut 提取 /etc/passwd 的用户名、UID(第 3 列)和登录 Shell,然后用 tr 将 /etc/hostname 的内容转为大写
  2. 进阶题(难度⭐⭐):用 awk 从 ls -l 中过滤出文件大小大于 1MB 的文件,然后用 sed 批量替换配置文件中的端口号(8080 → 9090),备份原文件
  3. 挑战题(难度⭐⭐⭐):创建两个略有不同的配置文件,用 diff -u 查看差异,然后写一个管道链从日志文件中提取所有错误行、按小时统计错误数量、输出 Top 5 高峰时段
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏