欢迎来到罐子的实验室
本次目标
理解命令行的「真正威力」——重定向和管道。学会把命令的输入输出像水管一样拼接、过滤、保存。
学完你能做什么:
- 理解 stdin / stdout / stderr 三个标准流
1. 三个标准流
每个 Linux 进程一出生就有三条「数据通道」,简称三个标准流:
| | | |
|---|
| 标准输入 | stdin | | |
| 标准输出 | stdout | | |
| 标准错误 | stderr | | |
为什么把它们分开?因为这样你就能分别处理正常结果和错误信息——比如把结果存文件,把错误显示在屏幕上。
文件描述符(FD) 是 Linux 给每条通道编的号。这三条是固定的 0/1/2。
2. 输出重定向:> 和 >>
> 把命令的标准输出写进文件,而不是显示在屏幕上:
user@host:~$ echo hello > greeting.txtuser@host:~$ cat greeting.txthello
屏幕上没有显示 hello——它被「截走」进了文件。
⚠️ > 会覆盖——如果文件已存在,原内容会被清空替换!
user@host:~$ echo first > f.txtuser@host:~$ echo second > f.txtuser@host:~$ cat f.txtsecond# first 没了!
想追加而不覆盖,用 >>:
user@host:~$ echo first > f.txtuser@host:~$ echo second >> f.txtuser@host:~$ cat f.txtfirstsecond
动手练习 1:写一个清单
任务:用 echo 把三行内容("买菜"、"洗碗"、"扔垃圾")追加写进 todo.txt,然后查看。
思路引导:第一行用 >(创建/覆盖),后两行用 >>(追加)。
参考答案:
user@host:~$ echo 买菜 > todo.txtuser@host:~$ echo 洗碗 >> todo.txtuser@host:~$ echo 扔垃圾 >> todo.txtuser@host:~$ cat todo.txt买菜洗碗扔垃圾
常见错误:第二行误用 > 把第一行覆盖了——分清 >(覆盖)和 >>(追加)。
3. 错误重定向:2>
回想 FD 编号:stdout 是 1,stderr 是 2。> 默认重定向的是 1,等价于 1>。要重定向错误,写 2>:
user@host:~$ ls /不存在的目录ls: cannot access '/不存在的目录': No such file or directory# 这条是 stderruser@host:~$ ls /不存在的目录 2> err.log# 错误进文件,屏幕上看不到了user@host:~$ cat err.logls: cannot access '/不存在的目录': No such file or directory
2> 同样会覆盖,2>> 是追加。
同时重定向 stdout 和 stderr
一个命令可以分别把正常输出和错误送到不同地方:
user@host:~$ ls /etc /不存在 > result.txt 2> error.txt
result.txt 里是 /etc 的列表,error.txt 里是关于 /不存在 的报错。
动手练习 2:分离错误
任务:运行 find / -name passwd,让正常结果留在屏幕上,但把所有 Permission denied 错误丢到 /dev/null(一个「黑洞」设备文件,写进去的东西都消失)。
思路引导:2> /dev/null 把 stderr 丢掉。
参考答案:
user@host:~$ find / -name passwd 2> /dev/null/etc/passwd/usr/share/base-files/passwd
清爽多了——之前刷屏的那些 Permission denied 全消失了。
4. 2>&1:把错误合并到输出
有时你想让 stdout 和 stderr 合并到一起。比如用管道把错误也传给 grep(管道默认只传 stdout,详见下节)。
2>&1 的意思是「把 FD2(stderr)重定向到 FD1(stdout)当前指向的地方」:
user@host:~$ ls /etc /不存在 > all.log 2>&1user@host:~$ cat all.logls: cannot access '/不存在': No such file or directory/etc 下的文件列表...
注意顺序很重要!Shell 从左到右处理重定向:
| |
|---|
> all.log 2>&1 | |
2>&1 > all.log | ❌ stderr 进屏幕,stdout 进 all.log(因为 2>&1 时 stdout 还指向屏幕) |
💡 还有个更简洁的写法:&> all.log 等价于 > all.log 2>&1,把两者都重定向到同一个文件。
动手练习 3:合并并保存
任务:把 find / -name "*.log" 的正常结果和错误都写进 find_result.log。
参考答案:
user@host:~$ find / -name ”*.log” &> find_result.log
5. 输入重定向:<
< 把文件内容喂给命令的标准输入,而不是让命令从键盘读:
user@host:~$ echo hello > msg.txtuser@host:~$ rev < msg.txt# rev 把输入反转olleh
很多命令既能用参数也能用 <,效果类似:
user@host:~$ cat msg.txt# 参数形式hellouser@host:~$ cat < msg.txt# 输入重定向形式hello
(对 cat 来说结果一样,但机制不同:前者 cat 自己打开文件,后者 Shell 把文件喂给 cat。)
动手练习 4:让命令读文件
任务:用 < 把 todo.txt 的内容喂给 wc -l(统计行数)。
参考答案:
user@host:~$ wc -l < todo.txt3
6. 管道:|
管道是命令行的灵魂。| 把左边命令的 stdout 连到右边命令的 stdin,让数据像水管一样流动:
user@host:~$ ls /etc | wc -l# 数 /etc 下有多少项243user@host:~$ ls /etc | grep pa# 找 /etc 下含 ”pa” 的项passwdpapersize...
管道可以串联很多级:
user@host:~$ ls /etc | grep ^s | sort | head
含义:列出 /etc → 留下 s 开头的 → 排序 → 取前几行。
💡 关键:管道只传 stdout,不传 stderr。想让错误也流过去,要先 2>&1 把它合并进 stdout(见第 4 节)。
动手练习 5:流水线查日志
任务:模拟一个日志文件(yes error | head -100000 > fake.log 造个 10 万行文件),用管道在一行命令里:读文件 → 找含 "error" 的行 → 数有多少条。
思路引导:cat fake.log | grep error | wc -l,或更简洁 grep error fake.log | wc -l。
参考答案:
user@host:~$ yes ”some error here” | head -100000 > fake.loguser@host:~$ grep error fake.log | wc -l100000
💡 这里 yes "..." 无限循环输出一行,head -100000 取前 10 万行(head 详见《模块 07》),重定向进文件。这是造测试数据的常用手法。
常见错误:
- ❌
cat file > grep xxx——> 是重定向到文件,不是管道。要传给命令用 |。 - ⚠️ 管道右边只能接到 stdin。如果右边命令必须用「文件名参数」(如
cp),管道就不管用,要用进程替换(见第 8 节)。
7. tee:分流器
tee(像水管的三通接头)把 stdin 同时输出到屏幕和一个或多个文件:
user@host:~$ echo hi | tee a.txt b.txthi# 屏幕显示user@host:~$ cat a.txthiuser@host:~$ cat b.txthi
经典用途:调试管道——你想看中间数据长啥样,又不想断开管道:
user@host:~$ ls /etc | tee debug.txt | grep pa | wc -l
debug.txt 里存了 ls /etc 的完整输出,供你事后查看。
tee -a 是追加模式(类似 >>)。
动手练习 6:边看边存
任务:运行 ls -l /etc,既要让结果在屏幕上滚过去,又要存一份到 etc_list.txt。
参考答案:
user@host:~$ ls -l /etc | tee etc_list.txt(屏幕上同时显示,文件里也有)
8. 进程替换:<(...) 和 >(...)
Linux 信奉「万物皆文件」。进程替换让你把命令的输出伪装成一个临时文件名,传给那些「只接受文件名参数」的命令。
输入进程替换:<(命令) —— 命令的输出变成了一个「文件路径」:
user@host:~$ echo <(echo hi)/dev/fd/63# bash 造了个临时路径user@host:~$ cat <(echo hi)# cat 当它是文件来读hi
最典型的应用——比较两个命令的输出,无需先存成临时文件:
user@host:~$ diff <(ls dir1) <(ls dir2)
这等价于「先存两个临时文件再 diff」,但一气呵成。
输出进程替换:>(命令) —— 把数据写进这个「路径」就等于喂给命令的 stdin:
user@host:~$ echo hi | tee >(wc -l) >(rev)hi1# wc -l 的结果ih# rev 的结果
动手练习 7:对比两份清单
任务:用进程替换,比较 /etc 和 /usr/etc(如果存在)下文件名的差异。
参考答案:
user@host:~$ diff <(ls /etc) <(ls /usr/etc)
💡 进程替换很强大但可读性差,别滥用。能用普通管道解决就别用它。
9. 命名管道(FIFO)
之前用的 | 是「匿名管道」,随用随建随拆。你也可以用 mkfifo 创建一个持久存在的命名管道(FIFO):
user@host:~$ mkfifo my_pipeuser@host:~$ ls -l my_pipeprw-r--r-- 1 user user 0 Jun 30 18:00 my_pipe# 权限开头的 p 表示 pipe
FIFO 的特别之处:写入端和读取端必须同时就绪,否则会「阻塞」等待。
打开两个终端:
终端 A(写):
user@host:~$ echo ”hello from the other side” > my_pipe# 这条命令会卡住,直到有人来读
终端 B(读):
user@host:~$ cat my_pipehello from the other side# 此时终端 A 的命令才结束
FIFO 用于跨进程实时通信——数据不落盘,读完即消失。日常用得不多,但理解它有助于理解管道的本质。
动手练习 8:体验阻塞
任务:在两个终端间用 FIFO 传一句话。
思路引导:一个终端 echo ... > my_pipe,另一个 cat my_pipe。
参考答案:见上面两个终端的示例。
常见错误:单终端里 echo x > my_pipe 会卡死等读端——这不是 bug,是 FIFO 的特性。Ctrl-C 退出。
本模块速查表
| |
|---|
命令 > 文件 | |
命令 >> 文件 | |
命令 2> 文件 | |
命令 2>> 文件 | |
命令 > 文件 2>&1 | |
命令 &> 文件 | |
命令 < 文件 | |
命令1 | 命令2 | 命令1 的 stdout → 命令2 的 stdin |
命令 | tee 文件 | |
<(命令) | |
2> /dev/null | |
三个流的 FD
FD 0 = stdin ← 进程读入FD 1 = stdout ← 正常输出FD 2 = stderr ← 错误输出
下一步
输出能存、能传、能丢了。但你常常还要加工这些数据——找关键字、替换、截取、排序。这就是文本处理工具的主场。