当前位置:首页>Linux>Linux 从0到1 : 06 · 输入输出与管道

Linux 从0到1 : 06 · 输入输出与管道

  • 2026-09-18 09:49:41
Linux 从0到1 : 06 · 输入输出与管道
 欢迎来到罐子的实验室
    今天来学习输入输出与管道

    本次目标

    理解命令行的「真正威力」——重定向和管道。学会把命令的输入输出像水管一样拼接、过滤、保存。

学完你能做什么:

  • 理解 stdin / stdout / stderr 三个标准流
  • 用 >>><2> 重定向输入输出
  • 用 | 把命令串成「流水线」
  • 用 tee、进程替换、命名管道处理复杂数据流

1. 三个标准流

每个 Linux 进程一出生就有三条「数据通道」,简称三个标准流:

流
缩写
文件描述符(FD)
作用
标准输入stdin
0
进程读入数据的地方(如键盘输入)
标准输出stdout
1
进程输出正常结果的地方(默认是屏幕)
标准错误stderr
2
进程输出错误信息的地方(默认也是屏幕)

为什么把它们分开?因为这样你就能分别处理正常结果和错误信息——比如把结果存文件,把错误显示在屏幕上。

文件描述符(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
✅ 两者都进 all.log
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 退出。


本模块速查表

操作
含义
命令 > 文件
stdout 覆盖写入文件
命令 >> 文件
stdout 追加到文件
命令 2> 文件
stderr 写入文件
命令 2>> 文件
stderr 追加
命令 > 文件 2>&1
stdout 和 stderr 都进文件
命令 &> 文件
同上的简写
命令 < 文件
把文件喂给命令 stdin
命令1 | 命令2
命令1 的 stdout → 命令2 的 stdin
命令 | tee 文件
边显示边存文件
<(命令)
把命令输出当文件
2> /dev/null
丢弃错误

三个流的 FD

FD 0 = stdin ← 进程读入FD 1 = stdout ← 正常输出FD 2 = stderr ← 错误输出

下一步

输出能存、能传、能丢了。但你常常还要加工这些数据——找关键字、替换、截取、排序。这就是文本处理工具的主场。

最新文章

随机文章