Linux: 管道与过滤器——Unix 哲学的精髓
Unix 哲学的核心是"一个程序只做一件事,做好它"。管道 | 将这些小程序串联起来——前一个程序的输出就是后一个程序的输入。
📋 前置知识:需要先掌握以下内容
- 第12课:标准流与重定向
1. 你将学到
- 管道
|的工作原理 - sort 排序与 uniq 去重
- wc 统计行/词/字符
- tee 双向输出
- 多级管道的组合艺术
2. 一个 10GB 日志文件的故事
(1) 痛点:日志太大,没法手动翻
小明需要从 10GB 的 Nginx 访问日志中找到访问量最高的前 10 个 IP。用编辑器打开不可能(10GB 打不开),用 Python 写脚本又太麻烦。
(2) 一行管道的威力
Bob 教他用管道组合:
BASH
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
ℹ️ 说明:
cat file | command虽然常见,但其实cat在这里只是把文件内容传给管道——直接用command < file或awk '{...}' file更高效,省去了一个进程。这被称为"UUOC"(Useless Use of Cat),虽然功能上没问题,但在追求性能时可以优化。
输出:
TEXT
📖 仅展示
45231 192.168.1.100
38902 10.0.0.45
21045 192.168.1.200
(3) 收益:复杂任务一行命令
小明发现管道组合几乎可以完成所有数据提取任务——统计、过滤、排序、排名、去重。一条命令就是一个小型数据处理流水线。
3. 知识点讲解
(1) 管道的工作原理
TEXT
📖 仅展示
command1 | command2 | command3
│ │ │
│ 输出 │ 输出 │
└──────────→│──────────→│──────────→ 最终输出
│ │
command1 的 stdout 连接到 command2 的 stdin
💡 提示:管道的工作原理是将前一个命令的 stdout 连接到后一个命令的 stdin——数据在内核缓冲区中流式传输,不需要写入磁盘。这意味着管道中的命令是并行运行的,处理大文件也不会撑爆内存。
(2) 常用过滤器
| 命令 | 作用 | 常用选项 |
|---|---|---|
sort |
排序 | -n 数字排序、-r 逆序、-k 按列、-u 去重后排序 |
uniq |
去重(相邻) | -c 计数、-d 只显示重复、-u 只显示不重复 |
wc |
统计 | -l 行数、-w 词数、-c 字符数 |
head |
显示开头 | -n 10 前 10 行、-c 100 前 100 字符 |
tail |
显示结尾 | -n 10 后 10 行、-f 实时追踪 |
tee |
分流 | 同时输出到文件和屏幕 |
💡 提示:
tee 的名字来自水管中的"T 型接头"——它将管道中的数据一分为二,一份继续往下传,一份写入文件。调试管道时,在中间插入 tee /tmp/debug.txt 可以捕获中间结果而不影响最终输出。
| nl | 加行号 | -ba 所有行都编号 |
▶ 示例:统计文件信息
BASH
# 统计文件数量
ls -la | wc -l
# 注意:wc -l 会包含 "total" 那一行,所以实际文件数要减 1
# 统计所有 .md 文件的总行数
cat *.md | wc -l
# 统计当前目录的磁盘占用
ls -lh | tail -n +2 | awk '{print $5, $9}'
输出:
TEXT 📖 仅展示15 342 4.0K config.yml 12M data.tar.gz
▶ 示例:排序与去重
TEXT
📖 仅展示
# 对 IP 地址排序(字母序和数字序的区别)
cat << EOF | sort
192.168.1.100
10.0.0.45
192.168.1.200
10.0.0.2
EOF
# sort -n 按数字排序(按 IP 段)
cat << EOF | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n
192.168.1.100
10.0.0.45
192.168.1.200
10.0.0.2
EOF
# 去重并计数
cat << EOF | sort | uniq -c | sort -rn
apple
banana
apple
orange
banana
apple
EOF
# 3 apple
# 2 banana
# 1 orange
输出:
TEXT 📖 仅展示10.0.0.2 10.0.0.45 192.168.1.100 192.168.1.200 3 apple 2 banana 1 orange
▶ 示例:head 和 tail
BASH
# 查看前 5 行
ls -la /etc | head -5
# 查看后 5 行
ls -la /etc | tail -5
# 显示第 10-20 行
cat longfile.txt | head -20 | tail -11
# 实时追踪日志(按 Ctrl+C 退出)
tail -f /var/log/syslog
# 查看最近 10 条日志
tail -f -n 10 /var/log/syslog
输出:
TEXT 📖 仅展示total 72 drwxr-xr-x 2 root root 4096 Jul 7 10:00 bin drwxr-xr-x 3 root root 4096 Jul 7 10:00 lib drwxrwxrwt 15 root root 4096 Jul 7 10:30 tmp
BASH
# 既在屏幕上看到结果,又保存到文件
ls -la | tee output.txt
# 追加模式
echo "新的内容" | tee -a output.txt
# 将管道中间结果保存
ps aux | tee processes.txt | grep nginx
# 先用 tee 保存所有进程到文件,再过滤出 nginx
输出:
TEXT 📖 仅展示total 72 drwxr-xr-x 2 root root 4096 Jul 7 10:00 bin ... 新的内容 root 1234 0.0 0.1 nginx: master
▶ 示例:实用管道组合
BASH
# 最大的 5 个文件
ls -lhS | head -6
# 当前运行的 shell 数量
ps aux | grep bash | wc -l
# 查看磁盘使用率超过 80% 的分区
df -h | grep -v "tmpfs" | awk '{print $5, $6}' | sort -rn
# 找出 /var/log 中 7 天内修改过的日志文件
find /var/log -name "*.log" -mtime -7 | sort
# 获取外网 IP
curl -s ifconfig.me | tee my-ip.txt
输出:
TEXT 📖 仅展示-r--r--r-- 1 root root 12K config.pkt -rw-r--r-- 1 root root 8.9M data.tar.gz 3 203.0.113.42
▶ 综合示例:日志分析管道链
BASH
# 模拟一个 Nginx 访问日志
cat << 'EOF' > /tmp/access.log
192.168.1.100 - - [07/Jul/2026:10:15:30 +0000] "GET /index.html HTTP/1.1" 200 1234
10.0.0.45 - - [07/Jul/2026:10:16:30 +0000] "GET /api/users HTTP/1.1" 200 5678
192.168.1.100 - - [07/Jul/2026:10:17:30 +0000] "POST /api/login HTTP/1.1" 401 234
10.0.0.45 - - [07/Jul/2026:10:18:30 +0000] "GET /api/users HTTP/1.1" 200 5678
192.168.1.200 - - [07/Jul/2026:10:19:30 +0000] "GET /index.html HTTP/1.1" 404 123
10.0.0.45 - - [07/Jul/2026:10:20:30 +0000] "GET /api/users HTTP/1.1" 200 5678
EOF
echo "=== 总请求数 ==="
wc -l /tmp/access.log
echo ""
echo "=== 每个 IP 的访问次数(Top 5)==="
cat /tmp/access.log | awk '{print $1}' | sort | uniq -c | sort -rn
echo ""
echo "=== 每个 HTTP 状态码的数量 ==="
cat /tmp/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
echo ""
echo "=== 访问最多的 URL ==="
cat /tmp/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -5
echo ""
echo "=== 404 错误的请求 ==="
grep " 404 " /tmp/access.log | awk '{print $1, $7}'
❓ 常见问题
Q: 管道是串行还是并行处理? A: \1
Q: 管道和重定向能一起用吗? A: 可以。例如
command > file 2>&1(重定向)和command | grep "error"(管道)可以组合:command 2>&1 | grep "error"。
Q: 管道中间出错会影响最终结果吗? A: 会。如果管道中某个命令失败,下游命令可能收到不完整数据。可以用
set -o pipefail(在脚本中)让管道中任何命令的失败都导致整体失败。
Q:
tail -f怎么退出? A: 按Ctrl + C。tail -f持续监控文件新内容,直到手动中断。
Q: 管道处理大文件会内存不足吗? A: \1
📖 小节
- 管道
|连接命令:前一个的 stdout → 后一个的 stdin sort排序、uniq -c去重计数、wc -l统计行数head -n看前 N 行、tail -f实时追踪、tee分流输出- 组合示例:
cat log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 - 管道是并行的、流式的,处理大文件也不会内存不足
📝 作业
- 基础题(难度⭐):用
ls -la | wc -l统计文件数量,然后用/var/log/syslog或创建一个测试文件,提取出包含 "error" 的行并统计数量 - 进阶题(难度⭐⭐):用
df -h | grep "^/" | sort -k5 -rn找出磁盘使用率最高的分区,并用tee将ps aux的结果同时保存到文件并在终端显示 - 挑战题(难度⭐⭐⭐):组合 3 个以上的管道命令完成一个数据处理任务——找出系统中最耗内存的 5 个进程,按内存占用降序排列,输出格式为"PID 命令 内存%"