管道是 Unix 哲学的核心体现:让每个程序只做一件事,做好一件事,然后通过管道把它们组合起来。管道符
|把一个命令的输出直接传给另一个命令作为输入。
管道的语法:
命令1 | 命令2 | 命令3 | ...
前一个命令的标准输出成为后一个命令的标准输入。
ls | grep ".txt"
这条命令把 ls 的输出传给 grep,grep 筛选出包含 ".txt" 的行。
管道在内核中创建一个缓冲区,一个进程往里面写,另一个进程从里面读。两个命令并行执行,数据流动起来。
cat large_file.txt | grep "error" | wc -l
cat 读取文件,grep 过滤包含 error 的行,wc 统计行数。三个命令同时运行,数据像流水一样经过每个处理环节。
ls | wc -l
ps aux | grep nginx
ls -l | less
cat big_file.txt | more
sort data.txt | uniq
sort data.txt | uniq -c | sort -rn
df -h | awk '{print $1, $5}'
ps aux | awk '{print $1}' | sort | uniq
cat access.log | grep "404" | wc -l
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
管道只传递标准输出,错误信息不会通过管道:
ls / /notexist | grep "root"
/notexist 的错误信息会显示在屏幕上,不会传给 grep。如果要处理错误信息,需要先重定向:
ls / /notexist 2>&1 | grep -v "无法访问"
tee 命令可以同时输出到屏幕和文件,相当于管道的"三通":
ls -l | tee filelist.txt
屏幕上显示 ls 的输出,同时保存到 filelist.txt。
追加模式:
echo "新日志" | tee -a log.txt
在脚本中记录处理过程:
cat data.txt | grep "error" | tee errors.txt | wc -l
管道中的命令在子 Shell 中执行,变量修改不会影响外部:
count=0
cat data.txt | while read line; do
count=$((count + 1))
done
echo "行数:$count"
运行结果:
行数:0
count 的修改丢失了。解决方法:
方法一:使用进程替换
count=0
while read line; do
count=$((count + 1))
done < <(cat data.txt)
echo "行数:$count"
方法二:使用 lastpipe 选项
shopt -s lastpipe
count=0
cat data.txt | while read line; do
count=$((count + 1))
done
echo "行数:$count"
方法三:用其他方式统计
count=$(wc -l < data.txt)
echo "行数:$count"
管道让多个命令并行执行,通常比中间文件更快:
# 使用管道
cat big.txt | grep "pattern" | sort | uniq > result.txt
# 使用中间文件
cat big.txt > temp1.txt
grep "pattern" temp1.txt > temp2.txt
sort temp2.txt > temp3.txt
uniq temp3.txt > result.txt
rm temp1.txt temp2.txt temp3.txt
管道版本更简洁,也更高效。
管道传递的是文本流,不适合处理二进制数据。对于复杂的处理逻辑,可能需要用 awk 或 Python 等工具。
管道中的每个命令都是独立的进程,进程间通信有一定开销。对于简单的操作,单条命令可能更快:
# 管道方式
cat file.txt | grep "pattern"
# 单命令方式(更快)
grep "pattern" file.txt
| 把一个命令的输出传给另一个命令