上一课你会写基本脚本了。但 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 正常列出文件,结果走 stdoutls 不存在的目录 报错,走 stderr。分开之后,你可以"只要结果、丢弃错误"(2>/dev/null),或"只要错误、不管结果"——灵活控制

27.2 重定向

一句话理解> file 2>&1 是"把正常和错误都写进同一个文件",日志脚本里几乎必用。

重定向,就是把某个流改接到文件

写法含义
>覆盖写入 stdout
>>追加写入 stdout
2>重定向 stderr
2>&1stderr 合并到 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

这条命令是一条流水线

  1. cat log 输出整个日志;
  2. grep ERROR 只留下含 ERROR 的行;
  3. wc -l 数一数有多少行。
管道是 Unix 哲学"每个程序做好一件事"的粘合剂——每个小工具只管一件事,用 | 串起来,就能组合出无穷的威力。你不需要一个大而全的程序,只需要会拼装小工具。

27.4 文本三剑客

工具干什么典型用法
grep按模式找行grep -i "error" loggrep -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)   # 比较两个目录的内容
<(...) 把命令的输出伪装成一个临时文件,喂给那些"只接受文件路径"的工具(如 diffsort)。

区别记法:

  • $():输出当字符串(塞进变量、字符串里);
  • <():输出当文件(喂给只吃文件的命令)。

动手实验

# 制造一份日志
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 块(处理完后执行)、关联数组做分组聚合——它是日志分析利器。想认真做数据分析,值得单独深挖。

② 进程替换解决"命令只能吃文件"

很多工具(diffsortjoin只接受文件路径,不接受标准输入。怎么办?

<(...) 把命令的输出伪装成临时文件,就能喂给它们了。比如 diff <(ls a) <(ls b),不需要先把两个目录列到临时文件里,直接比较。

小结与思考题

这一课,我们学会了"串流水线":

  • 三个标准流:stdin(0)、stdout(1)、stderr(2);
  • 重定向> >> 2> 2>&1 <> file 2>&1 是日志标配;
  • 管道A | B 把输出接输入,Unix 哲学的粘合剂;
  • 三剑客:grep 筛行、sed 改内容、awk 按列算;
  • $()<():输出当字符串 vs 输出当文件。

留三个问题:

  1. stdout 和 stderr 的区别?2>&1 是什么?
  2. grep / sed / awk 各自擅长什么?
  3. $()<() 的区别?
现在你手上有了一整套 shell 武器:变量、判断、循环、函数、管道、三剑客。但光会写还不够——一个真正能上生产的脚本,还得健壮(出错能处理)、能定时自动跑(cron)、好排错。下一课,我们把这几课的知识合成一个真正的自动化运维脚本,并配上定时任务,让它在无人值守时也能按时干活。

标签: 计算机基础, 操作系统, Linux

添加新评论