终端里到底有什么 书架

第 06 章 / 共 12 章

第六章 · 管道与重定向

第五章说,shell 启动程序时会「准备好 stdin/stdout 两根管子」。这一章揭晓:这两根管子不是焊死的,可以随手拔下来接到别处——接到文件、接到另一个程序、接到垃圾桶。Unix 命令行一半的威力来自这一个动作。

三根管子有编号:0、1、2

每个进程出生自带三根管子,内核给它们编了号,叫文件描述符:0 是标准输入(stdin),1 是标准输出(stdout),2 是标准错误(stderr)。编号这件事马上就有用。

为什么错误要单走一根管?因为输出可能是「数据」,错误只是「说给人听的话」。想象 ls 一个目录,文件名是数据,「没权限」是抱怨——如果混在一起,下游程序就分不清哪些行是文件名、哪些行是骂街。分开走,数据进管道,抱怨上屏幕,两不耽误。

重定向:把管子接到文件上

ls > files.txt 的意思是:shell 启动 ls 之前,把它的 1 号管子从终端上拔下来,插到文件 files.txt 上。ls 自己毫无知觉——它还是照常往 1 号管写字节,只是字节落进了文件。>> 是接到文件末尾(追加),< 是把 0 号管接到文件(程序从文件读,不从键盘读)。

最绕的是 2>&1,读作「把 2 号管接到 1 号管现在插着的地方」。注意「现在」两个字——次序有坑:

理解重定向就记住一个画面:shell 在启动程序前挨个儿插拔管子,动作严格从左到右。&1 接的不是「1 号管」,是「1 号管此刻插着的那个口」。

还有个著名的口:/dev/null,系统的垃圾桶,插进去的字节直接蒸发。cmd 2> /dev/null 就是「别跟我抱怨」。

管道:两个程序首尾相接

cat access.log | grep 500 | wc -l ——数一下日志里有多少行含 500。竖线 | 干的事:让 shell 向内核要一根「匿名管道」(内核内存里的一段缓冲区),把左边程序的 1 号管接进管道口,把右边程序的 0 号管接到管道另一头。于是 cat 吐的字节直接流进 grep,grep 筛完的字节直接流进 wc。三个程序同时跑,像流水线上并排站着的三个工人,不是「跑完一个再跑下一个」。

几个由此而来的真相:

程序会偷看管子那头是什么:isatty

做个实验:ls 直接跑,文件名排成多列、还可能带颜色;ls | cat 接进管道,变成一行一个、颜色消失。同一个程序,两种输出——它怎么知道的?

程序启动后可以问内核一个问题:「我的 1 号管那头是终端吗?」(这个询问叫 isatty)。是终端,说明对面是人,输出排版漂亮点、加上颜色暗号;是管道或文件,说明对面是另一个程序,就老老实实一行一个、不带任何花哨——花哨的转义序列对下游程序是噪音。

这解释了一大批日常困惑:为什么重定向到文件后颜色没了(故意没的,--color=always 能强留);为什么有些命令接进管道后「进度条」没了(进度条是给人看的);为什么脚本里跑得好好的命令交互下长得不一样。

乐高哲学

退后一步看:重定向和管道之所以威力无穷,是因为第五章说的那个设计——程序只认字节流,不认设备。写文件的程序、写屏幕的程序、写网络的程序,写法一模一样,换的只是管子插在哪。每个小程序只干一件事,shell 负责把它们插成任意组合。这就是所谓 Unix 哲学:不做万能的大工具,做能互相插接的小零件。

不过有个问题我们绕了很久了:程序问「我的 1 号管那头是终端吗」——图形界面时代,根本没有硬件终端,内核拿什么回答这个问题?答案是内核撒了一个美丽的谎,这就是下一章的假终端。

终端里到底有什么
费曼式写法 · 由多个 AI 代理撰写与互相审校