凡人修云传(三:重定向管道和tee)
1. 三大流派:stdin、stdout、stderr的江湖地位
在正式开始之前,先讲个段子。当年我刚接触Linux的时候,看着命令一行一行跑完,屏幕上冒出一堆文字,我一直以为这是命令的“回应”。直到有一天,我把一个命令的输出重定向到了文件,结果屏幕上还是哗啦啦地冒出一堆东西,我当时就懵了:我不是已经把输出写到文件里了吗,怎么还在刷屏?
后来才搞明白,Linux下任何命令向外吐东西,其实有三条通道,而不是一条。
- 标准输入(stdin,文件描述符0):命令从哪儿读数据,默认从键盘读。
- 标准输出(stdout,文件描述符1):命令把正常结果往哪儿写,默认写到屏幕。
- 标准错误(stderr,文件描述符2):命令把报错信息往哪儿写,默认也是写到屏幕。
这三条通道,就是Linux世界里最基础的“三大流派”。它们默认都指向终端,但我们可以通过重定向、管道、tee这些手段,任意改变它们的流向。
理解这三者的区别,是理解后面所有操作的前提。很多新手刚接触shell时,最大的认知障碍就是:以为“输出”就只有一种,其实正确结果和错误信息是分开走的。举个例子:
bash复制ls /etc/passwd /etc/not_exist_file
执行结果里,/etc/passwd那行是正常输出(stdout),而ls: cannot access '/etc/not_exist_file'那句是错误输出(stderr)。它俩都打在屏幕上,但来源不同。如果你只把stdout重定向到文件,错误信息还是会直接怼到屏幕上;如果你只把stderr重定向到文件,正常结果又会漏出来。明白了这一点,你才能真正掌控命令的输出行为。
而管道和tee,本质上都是在操作这三条通道的流向。管道负责把一个命令的stdout接到另一个命令的stdin,tee则负责在管道中间开一个口子,把流过的数据分身一份保存下来。
今天这篇文章,就用修仙的比喻把这些东西彻底讲透。你可以把每个Linux命令想象成一个修炼者,stdout是它吐出的一口真气,stderr是它憋不住吐出来的一口老血,而重定向、管道、tee就是控制这些气流走向的经脉和法门。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 重定向:基础中的基础,但细节多到能坑死人
2.1 输出重定向:> 和 >> 的差别
先说最常用的>和>>,它们都是把标准输出指向文件,区别在于一个是覆盖,一个是追加。
bash复制# 把date命令的输出写入time.txt,如果文件已存在则覆盖
date > time.txt
# 把uptime的输出追加到time.txt末尾,不会清空原内容
uptime >> time.txt
这个太基础了,但有几个细节我想强调一下。
第一,>和>>如果文件不存在,都会自动创建文件。我见过不少同学以为必须先touch一下文件才能重定向,其实完全没必要。
第二,>的覆盖行为是不可逆的。如果你本来有个重要的配置文件,执行了echo "hello" > config.conf,那就真没了。所以我在生产环境操作时,有个习惯:凡是涉及覆盖已有重要文件的重定向,先用ls -l看一眼,或者干脆改用>>,确保不会误伤。
第三,重定向符号和命令之间的空格问题。date > time.txt写成date >time.txt、date> time.txt都没问题,但为了可读性,建议统一带空格。不过要注意,>time.txt如果紧跟命令名后面,有的shell解析起来会有歧义,虽然bash能处理,但少给自己挖坑。
2.2 错误输出重定向:2> 和 2>>
2>就是专门把标准错误重定向到文件。这在脚本和定时任务里是标配,几乎每一个crond配置或者systemd timer里都会见到。
bash复制# 只把错误信息存下来,正常输出不要
./deploy.sh 2> error.log
# 把正常输出和错误输出分别存到不同文件
./deploy.sh 1> success.log 2> error.log
# 把错误信息追加到日志文件
./deploy.sh 2>> deploy.log
为什么要把错误和正确分开?因为排查问题时,你只想看报错信息,不想在一堆正常日志里翻找。而判断一个脚本是否成功,有时候恰恰要靠错误日志来判断。分开存,出问题的时候直接看error.log,效率高得多。
2.3 常见误区:2>&1 到底该怎么写?
这里有一个经典考题:怎么把标准输出和标准错误都重定向到同一个文件?
很多新手会写command > file 2> file,这个写法看起来对,但实际上是个坑。两个文件描述符各自独立打开同一个文件进行写入,带有不同的文件偏移量,在高并发或大量写入时,会互相覆盖内容。正确做法是:
bash复制command > file 2>&1
这个2>&1的意思是把标准错误重定向到标准输出当前指向的位置。注意,它必须出现在> file之后,因为它是“把错误输出指向标准输出的去向”,而标准输出去向要靠前面的> file来指定。
如果你写成command 2>&1 > file,执行顺序会变成:先把标准错误指向屏幕(因为它继承了当前标准输出的位置,即终端),再把标准输出指向文件。结果就是错误信息依然打到屏幕上,完全没有重定向。这个顺序问题,我见过无数人栽过跟头,包括我自己。
bash还提供了一种更简洁的写法:
bash复制command &> file
&>是同时重定向stdout和stderr到文件,不需要区分顺序。在较新版本的bash里,&>>表示同时追加。不过要注意,&>这种写法在POSIX sh里不支持,如果你要写兼容性很强的脚本,尽量用2>&1的写法。
2.4 输入重定向:< 和 << 与 <<<
输出重定向大家用得熟,输入重定向很多人反而忽略了。
<表示从一个文件读入数据作为
