操作系统学习笔记 · 第 27 课 · 管道、重定向与文本三剑客
上一课你会写基本脚本了。但 shell 的真正威力,在于把一堆小工具串成流水线——用一行命令,从几万行日志里筛出想要的东西。这一课,我们讲 管道、重定向,以及那三个让文本处理如虎添翼的工具——grep / sed / awk(合称"文本三剑客")。这是 shell 最硬核、也最实用的部分。
从"三条水管"说起
每个程序,天生带着三条"水管",用来跟外界交换数据:
- stdin(标准输入):数据从哪进来;
- stdout(标准输出):正常结果从哪出去;
- stderr(标准错误):错误信息从哪出去。
默认情况下,这三条水管都接在终端上——你敲键盘是 stdin,屏幕显示是 stdout 和 stderr。但你可以把它们改接到别处(重定向),或者把一个程序的输出接到另一个程序的输入(管道)。这就是本课的主题。
27.1 三个标准流
一句话理解:每个程序都有三个"流"——stdin(0,输入)、stdout(1,正常输出)、stderr(2,错误输出),默认都连到终端。
每个流有个编号:
- 0 = stdin(输入);
- 1 = stdout(正常输出);
- 2 = stderr(错误输出)。
为什么要分开 stdout 和 stderr?
ls正常列出文件,结果走 stdout;ls 不存在的目录报错,走 stderr。分开之后,你可以"只要结果、丢弃错误"(2>/dev/null),或"只要错误、不管结果"——灵活控制。
27.2 重定向
一句话理解:> file 2>&1 是"把正常和错误都写进同一个文件",日志脚本里几乎必用。重定向,就是把某个流改接到文件:
| 写法 | 含义 |
|---|---|
> | 覆盖写入 stdout |
>> | 追加写入 stdout |
2> | 重定向 stderr |
2>&1 | stderr 合并到 stdout |
< | 从文件读 stdin |
几个关键:
>是覆盖(清空再写),>>是追加(接着写);2>/dev/null把错误信息丢进黑洞(/dev/null还记得吗?第 20 课的黑洞设备);2>&1把 stderr 合并到 stdout——日志脚本几乎必用。
最常用的组合:命令 > file 2>&1,把正常和错误都写进同一个文件。27.3 管道 |
一句话理解:A | B把 A 的 stdout 接到 B 的 stdin,像流水线——cat log | grep ERROR | wc -l一路过滤统计。
管道 | 是 Unix 的灵魂:
cat log | grep ERROR | wc -l这条命令是一条流水线:
cat log输出整个日志;grep ERROR只留下含 ERROR 的行;wc -l数一数有多少行。
管道是 Unix 哲学"每个程序做好一件事"的粘合剂——每个小工具只管一件事,用 | 串起来,就能组合出无穷的威力。你不需要一个大而全的程序,只需要会拼装小工具。27.4 文本三剑客
| 工具 | 干什么 | 典型用法 |
|---|---|---|
| grep | 按模式找行 | grep -i "error" log、grep -o |
| sed | 流编辑(替换/删除) | sed 's/foo/bar/g' |
| awk | 按列处理 | awk '{print $1, $3}' |
一句话理解:grep 负责"筛行",sed 负责"改内容",awk 负责"按列算"——三者组合能覆盖绝大多数文本处理。
grep——筛出匹配的行:
grep "error" app.log # 找含 error 的行
grep -i "error" app.log # -i 忽略大小写
grep -o "..." # -o 只输出匹配的部分sed——流式编辑(替换、删除):
sed 's/foo/bar/g' # 把所有 foo 替换成 bar(g=全局)
sed 's/foo/bar/' # 每行只替换第一个
sed '/^$/d' # 删除空行awk——按列处理:
awk '{print $1, $3}' # 打印每行的第 1、3 列(默认按空格分列)记住分工:grep 筛行、sed 改内容、awk 按列算。
27.5 命令替换与进程替换
一句话理解:$(...)把命令的输出当字符串用,<(...)把命令的输出当临时文件用。
命令替换 $(...):
today=$(date +%F) # 把 date 的输出存进变量 today
echo "今天是 $today"$(...) 先执行里面的命令,把输出结果替换到那个位置,当字符串用。进程替换 <(...):
diff <(ls a) <(ls b) # 比较两个目录的内容<(...)把命令的输出伪装成一个临时文件,喂给那些"只接受文件路径"的工具(如diff、sort)。
区别记法:
$():输出当字符串(塞进变量、字符串里);<():输出当文件(喂给只吃文件的命令)。
动手实验
# 制造一份日志
for i in {1..100}; do
if (( i % 3 == 0 )); then echo "ERROR $i"; else echo "OK $i"; fi
done > app.log
grep ERROR app.log | wc -l # 数错误行
sed 's/ERROR/WARN/' app.log > app2.log # 批量替换
awk '$1=="ERROR" {print $2}' app.log # 打印错误行的第 2 列
today=$(date +%F); echo "今天是 $today" # 命令替换观察:一行命令就能从日志里筛出错误、改内容、按列取值——这就是"管道 + 三剑客"的威力。
这份日志里,$i % 3 == 0的行是 ERROR(3、6、9…共 33 行),grep ERROR | wc -l会数出 33。awk '$1=="ERROR" {print $2}'会打印这些行的第 2 列(也就是那些数字)。
深入点:两个进阶话题
① awk 其实是门语言
awk 远不止 print:
awk '{sum+=$1} END{print sum}' # 累加第 1 列,最后打印总和awk支持变量、累加、END块(处理完后执行)、关联数组做分组聚合——它是日志分析利器。想认真做数据分析,值得单独深挖。
② 进程替换解决"命令只能吃文件"
很多工具(diff、sort、join)只接受文件路径,不接受标准输入。怎么办?
<(...)把命令的输出伪装成临时文件,就能喂给它们了。比如diff <(ls a) <(ls b),不需要先把两个目录列到临时文件里,直接比较。
小结与思考题
这一课,我们学会了"串流水线":
- 三个标准流:stdin(0)、stdout(1)、stderr(2);
- 重定向:
>>>2>2>&1<,> file 2>&1是日志标配; - 管道:
A | B把输出接输入,Unix 哲学的粘合剂; - 三剑客:grep 筛行、sed 改内容、awk 按列算;
$()和<():输出当字符串 vs 输出当文件。
留三个问题:
- stdout 和 stderr 的区别?
2>&1是什么? - grep / sed / awk 各自擅长什么?
$()和<()的区别?
现在你手上有了一整套 shell 武器:变量、判断、循环、函数、管道、三剑客。但光会写还不够——一个真正能上生产的脚本,还得健壮(出错能处理)、能定时自动跑(cron)、好排错。下一课,我们把这几课的知识合成一个真正的自动化运维脚本,并配上定时任务,让它在无人值守时也能按时干活。