本文要点
- 管道
|:把前一条命令的stdout直接接到下一条命令的stdin,命令之间无需落盘,数据在内存中流转 - 文件描述符:0 是
stdin、1 是stdout、2 是stderr;>覆盖写、>>追加写、2>重定向错误输出、2>&1把错误并进标准输出 - 退出码陷阱:管道整体只返回最后一条命令的状态;用
${PIPESTATUS[@]}看每段的退出码,脚本里建议set -o pipefail让任一段失败即整体失败 - tee:数据流「一边进文件、一边上屏幕」,是观察管道中间结果的利器
- heredoc 与进程替换:
<<'EOF'多行文本输入,<(命令)把命令输出当成文件用(如diff <(…)) - 所有命令均在 Ubuntu 24.04(内核 6.8)上真实执行验证,截图即真实输出
管道:把命令串成流水线
Linux 哲学是「一个命令只做一件事」,但现实中的任务往往要好几步:过滤、统计、排序、取前几行。把这些步骤串起来的就是管道 |——它的作用是把前一条命令的标准输出(stdout),直接作为下一条命令的标准输入(stdin)传过去。
echo "hello linux world" | tr 'a-z' 'A-Z'tr 'a-z' 'A-Z' 把小写转大写,它自己不产生数据,只消费 stdin。上面这条命令输出 HELLO LINUX WORLD。数据没有经过任何临时文件——管道是内存里的一个缓冲区,这也是管道比「先存文件再读文件」快的原因。

三个最常见的管道场景是过滤、计数、提取:
ls /usr/bin | wc -lls 列出几百个文件名,wc -l 统计行数——得到系统里可执行命令的总数(demo 机上 /usr/bin 有 1371 个条目)。
ps -e --no-headers | wc -lps -e 列出全部进程,--no-headers 去掉表头,接上 wc -l 就是当前系统的进程总数(186 个)。
管道还能串很多级,比如统计 dpkg 软件包列表里各状态的出现次数,从大到小排序后取前 5:
dpkg -l | awk '{print $1}' | sort | uniq -c | sort -rn | head -5这一串做了五件事:dpkg -l 列出所有已安装软件包 → awk 只取第一列状态码 → sort 排序 → uniq -c 去重并计数 → sort -rn 按次数倒序 → head -5 取前 5。输出里 957 ii 表示 957 个包处于正常的「已安装」状态(i=installed,第二个 i 是期望状态),5 rc 是 5 个已删除但残留配置的包(r=removed, c=config-files)。
管道可以无限串联,但它有一个重要的隐藏规则——每个管道段是同时启动的子进程,不是等前一个跑完再跑下一个。所以写管道时不用担心中间结果太大撑爆内存,数据是流式处理的。
重定向:把输出写到文件、把错误藏起来
管道接的是「命令 ↔ 命令」,而重定向接的是「命令 ↔ 文件」。Linux 里每个进程都带着三个默认打开的文件描述符(file descriptor,FD):
| 描述符 | 名称 | 默认去向 |
|---|---|---|
| 0 | stdin 标准输入 | 键盘 |
| 1 | stdout 标准输出 | 屏幕 |
| 2 | stderr 标准错误 | 屏幕 |
注意:stderr 默认也显示在终端上,但它和 stdout 是两个独立通道。这是很多人踩坑的地方——命令 > file 只重定向了 stdout,错误信息照样打在屏幕上。覆盖写 > 与追加写 >>:
echo "first line" > /tmp/demo.txt
cat /tmp/demo.txt
echo "second line" >> /tmp/demo.txt
cat /tmp/demo.txt> 会先清空文件再写入,所以第二次 cat 看到两行——第一行来自 >,第二行是 >> 追加进去的。>> 不会清空已有内容,追加在末尾。运维里「> 误清空日志」是事故高发区,写脚本前先想清楚要不要保留旧内容。
重定向 stderr:2>:
ls /nonexistent 2> /tmp/demo_err.txt
cat /tmp/demo_err.txtls 找不到目录时,错误信息走的是 stderr。2> 把 FD 2 重定向到文件,屏幕干干净净,错误被写进 demo_err.txt。注意管道 | 只接 stdout——错误默认还是会打到你面前:
ls /nonexistent > /dev/null 2>&1; echo "exit=$?"这里出现两个新东西:/dev/null 是 Linux 的「黑洞设备」,写进去的数据直接丢弃;2>&1 是把 stderr 并到 stdout 的同一个去向(顺序很重要:>&1 必须出现在 > 之后,因为它是「把 2 指向 1 当前的位置」)。合起来 > /dev/null 2>&1 就是经典的一句「我不要任何输出」,常用于静默执行命令。注意 ls /nonexistent 的退出码是 2——命令失败时输出不一定有,但退出码一定诚实。

管道与退出码:$? 的小陷阱
自动化脚本里,$? 是上一条命令的退出码(0 成功、非 0 失败)。但管道有一个隐蔽行为:$? 只反映最后一条命令的状态:
false | true; echo "last rc=$?"false 永远返回 1,true 永远返回 0。上面的输出是 last rc=0——管道"吞掉"了 false 的失败!如果中间任何一段命令失败而你只检查 $?,就会被误判为成功。
好在 Bash 提供了 PIPESTATUS 数组,记录管道里每一段的退出码:
false | true; echo "PIPESTATUS=${PIPESTATUS[@]}"输出 PIPESTATUS=1 0:第一段退出码 1(false 的失败被如实记录),第二段 0。注意 PIPESTATUS 要用 ${PIPESTATUS[@]} 展开,并且要在管道命令后立刻读取——任何中间命令都会重置它。
写脚本时更省心的做法是开启 pipefail 选项,让管道整体失败(返回非 0):
set -o pipefail
false | true; echo "pipefail rc=$?"
set +o pipefail开启后管道返回所有段中最后一个非 0 的退出码,false | true 整体变成失败。日常交互 shell 不必开,但在备份、部署脚本开头加上 set -o pipefail 是很好的习惯——配合 set -e 可以让任何一段失败都立刻中止脚本,而不是带着坏数据继续跑。

tee:一边写文件一边看输出
调试管道时常见需求:想知道中间结果,又不想打断管道链。tee 就是干这个的——数据流过它时,一份写进文件、一份继续输出到屏幕:
echo "hello tee" | tee /tmp/demo_tee.txt屏幕上出现 hello tee,文件里也存了同一行。把 tee 插进管道任意位置就能"偷看"中间数据:
ps -e | tee /tmp/ps_snapshot.txt | wc -l先落一份快照再继续统计。tee -a 追加模式、tee /dev/tty 只显示不落盘等变体,在调试多级管道时都很实用。
heredoc 与进程替换:更高级的输入方式
heredoc(<<)把一段多行文本作为命令的 stdin,适合向脚本、配置文件喂入多行内容:
cat <<'EOF' > /tmp/demo_heredoc.txt
line 1 from heredoc
line 2 from heredoc
EOF
cat /tmp/demo_heredoc.txt<<'EOF' 到单独一行的 EOF 之间的内容原样成为命令输入,文件里得到两行文本。两点提醒:定界符(这里用的 EOF)必须单独占一行;引号里的定界符 'EOF' 表示内容不做变量展开,如果想在内容里用 $HOME 之类的变量,就去掉引号写成 <<EOF。
进程替换(<(...))把命令的输出包装成"看起来像文件"的东西,适用于那些要求文件参数而不是 stdin 的命令。最经典的例子是 diff 直接比较两条命令的输出:
diff <(seq 1 3) <(seq 1 4)seq 1 3 输出 1、2、3,seq 1 4 输出 1、2、3、4——diff 报告 3a4 / > 4,即第三行后面新增了第 4 行。进程替换的实质是 Bash 用 /dev/fd/N 临时文件描述符实现的,它还有一个反向兄弟 >(命令):把一段数据流"灌进"某条命令而不是读出来,比如给只认文件参数的工具喂数据(语法细节见文末的 Bash 手册链接,注意它依赖 Bash 支持,脚本里需要声明 #!/usr/bin/env bash)。
小结
管道与重定向是 Linux 命令行最核心的数据流工具,记住三个要点就够用:
|接 stdout,>接文件;stderr 是独立通道,需要时用2>、2>&1显式处理。- 管道只回报最后一段的退出码;关心中间段就用
${PIPESTATUS[@]},脚本里用set -o pipefail兜底。 tee偷看中间结果,heredoc 喂多行输入,<()把输出当文件——这三个技巧能让管道从"能跑"变成"好调试"。
想继续深入数据流的组合玩法,可以接着看《Linux 文本处理三剑客:grep、sed、awk》里的管道应用、《Linux sort 与 uniq 排序去重》的统计管道,以及《xargs 命令行》怎么把管道输出转成命令参数。写自动化脚本时,《Bash 脚本编程》里还有 set -e、set -u 等配套选项值得一并了解。
评论 (0)
暂无评论,快来抢沙发吧!