Linux 管道命令使用笔记
一、管道是什么
管道(pipe)是 Linux 中一种强大的 IPC(进程间通信)机制,使用竖线符号 | 表示。它将前一个命令的标准输出(stdout)直接连接到后一个命令的标准输入(stdin),使数据可以在多个命令之间流动,而无需创建临时文件。
管道是 Unix 哲学"每个程序只做好一件事"的核心支撑------通过管道将简单工具组合起来,可以完成复杂的数据处理任务。
二、基本语法与原理
2.1 基本语法
bash
command1 | command2 | command3
command1 的 stdout → command2 的 stdin → command3 的 stdin。
2.2 管道的底层原理
管道在内核中维护一个环形缓冲区(通常为 64KB,由 PIPE_BUF 定义)。写入端将数据放入缓冲区,读取端从中取出数据。管道是单向 的、半双工的通信通道。
关键特性:
- 管道两端的命令是并发执行的,而非串行
- 如果管道缓冲区满了,写入命令会被阻塞
- 如果管道为空且写入端已关闭,读取命令会收到 EOF
- 管道中的命令运行在独立的子进程中(子 shell)
2.3 管道的退出状态
默认情况下,管道的退出状态是最后一个命令 的退出状态。使用 $? 查看:
bash
false | true
echo $? # 输出 0(true 的退出状态)
Bash 提供了 pipefail 选项,让管道中任何命令失败时整个管道都返回失败:
bash
set -o pipefail # 启用
set +o pipefail # 禁用
也可以使用 PIPESTATUS 数组获取管道中每个命令的退出状态:
bash
command1 | command2 | command3
echo "${PIPESTATUS[@]}" # 输出各命令的退出码
三、常用管道组合
3.1 grep --- 文本过滤
bash
# 在命令输出中搜索关键字
ps aux | grep nginx
dmesg | grep -i error
ls -la | grep "^d" # 只列出目录
cat /etc/passwd | grep "/bin/bash" # 查找使用 bash 的用户
history | grep "docker" # 搜索历史命令
3.2 sort --- 排序
bash
du -sh * | sort -h # 按人类可读的大小排序
ps aux | sort -k3 -n -r # 按 CPU 使用率降序排序
cat words.txt | sort | uniq # 排序后去重
ls -la | sort -k5 -n # 按文件大小(第5列)数字排序
3.3 uniq --- 去重
bash
# uniq 只能去除相邻的重复行,通常与 sort 配合使用
sort data.txt | uniq # 去重
sort data.txt | uniq -c # 去重并统计出现次数
sort data.txt | uniq -d # 只显示重复的行
sort data.txt | uniq -u # 只显示唯一的行
3.4 wc --- 统计
bash
ls -1 | wc -l # 统计文件数量
ps aux | wc -l # 统计进程数量
cat file.txt | wc -c # 统计字节数
cat file.txt | wc -w # 统计单词数
find . -name "*.go" | wc -l # 统计 Go 文件数量
3.5 head / tail --- 取头部/尾部
bash
ps aux | head -5 # 前 5 行
ps aux | tail -5 # 后 5 行
ps aux | tail -n +2 # 从第 2 行开始(跳过表头)
dmesg | tail -20 | grep -i error # 查看最近的错误日志
3.6 cut --- 列提取
bash
cat /etc/passwd | cut -d: -f1,7 # 提取用户名和 shell
ls -la | tr -s ' ' | cut -d' ' -f5-9 # 提取文件大小和名称
ip addr | grep "inet " | cut -d' ' -f6 # 提取 IP 地址
3.7 awk --- 强大的文本处理
bash
ps aux | awk '{print $2, $11}' # 打印 PID 和命令
df -h | awk '$5 > 80 {print $0}' # 找出磁盘使用率超过 80% 的分区
ps aux | awk '{sum+=$3} END {print sum}' # 计算 CPU 总使用率
docker ps | awk 'NR>1 {print $NF}' # 提取容器名称(跳过表头)
3.8 sed --- 流编辑器
bash
ls -la | sed 's/^/ /' # 每行前面加两个空格
ps aux | sed -n '2,5p' # 打印第 2 到 5 行
cat config.ini | sed 's/= /= /g' # 替换文本
cat file.txt | sed '/^$/d' # 删除空行
3.9 tr --- 字符转换
bash
echo "Hello World" | tr '[:lower:]' '[:upper:]' # 转大写
cat file.csv | tr ',' '\t' # 逗号转制表符
cat file.txt | tr -s '\n' # 压缩空行
cat file.txt | tr -d '\r' # 删除 Windows 换行符
3.10 xargs --- 将标准输入转为命令行参数
某些命令(如 kill、rm、mv)不接受标准输入作为参数,xargs 可以解决这个问题:
bash
# 查找并删除所有 .tmp 文件
find . -name "*.tmp" | xargs rm
# 查找并杀掉进程
ps aux | grep "python" | awk '{print $2}' | xargs kill -9
# 批量创建目录
echo "dir1 dir2 dir3" | xargs mkdir
# 使用 -I 自定义占位符(更安全)
find . -name "*.log" | xargs -I {} mv {} /tmp/
# 使用 -n 限制每次传递的参数数量
echo {1..100} | xargs -n 10 echo
# 与 -P 配合并行执行
cat urls.txt | xargs -P 4 -I {} curl -O {}
注意 :文件名包含空格时使用
find ... -print0 | xargs -0来正确处理。
3.11 tee --- 分流输出
tee 从标准输入读取数据,同时写入标准输出和一个或多个文件------就像水管的三通接头:
bash
# 同时输出到屏幕和文件
command | tee output.txt
# 追加到文件
command | tee -a output.txt
# 同时写入多个文件
command | tee file1.txt file2.txt
# 管道中间插入 tee 来调试
command1 | tee debug.log | command2 | command3
四、高级管道技巧
4.1 命名管道(FIFO)
匿名管道(|)只能在有亲缘关系的进程间通信。命名管道(FIFO)允许任意两个进程通信:
bash
# 创建命名管道
mkfifo mypipe
# 终端 1:写入数据
echo "Hello from another terminal" > mypipe
# 终端 2:读取数据
cat < mypipe
实用场景:
bash
# 实时处理日志流
mkfifo /tmp/logpipe
tail -f /var/log/nginx/access.log > /tmp/logpipe &
grep "404" < /tmp/logpipe | cut -d' ' -f1
4.2 进程替换(Process Substitution)
使用 <(command) 或 >(command) 可以让命令像文件一样被引用:
bash
# 比较两个命令的输出差异
diff <(ls dir1) <(ls dir2)
# 比较排序后的文件
comm <(sort file1.txt) <(sort file2.txt)
# 合并多个命令的输出
paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f7 /etc/passwd)
# 将输出发送到进程替换
echo "data" > >(gzip > output.gz)
<( ) 实际上是创建了一个命名管道并传递文件描述符给命令。
4.3 多重管道与子 shell
bash
# 将管道输出赋值给变量
total_lines=$(cat file.txt | wc -l)
echo "文件有 $total_lines 行"
# 使用子 shell 组合多个命令
(cat header.txt; cat body.txt) | grep "keyword"
# 将标准错误也导入管道(bash 中)
command 2>&1 | grep "error"
# 只管道传递标准错误
command 2>&1 >/dev/null | grep "error"
4.4 条件管道
bash
# 在管道中使用 if 语句
cat /etc/passwd | while IFS=: read -r user _ uid _ _ home shell; do
if [ "$uid" -ge 1000 ] && [ "$uid" -lt 60000 ]; then
echo "普通用户: $user (UID=$uid, 家目录=$home)"
fi
done
# 使用 awk 进行条件过滤
df -h | awk 'NR==1 || $5+0 > 80' # 保留表头 + 高使用率行
4.5 管道与循环
bash
# 注意:管道中的循环运行在子 shell 中,对变量的修改不会影响外部
counter=0
seq 1 5 | while read n; do
counter=$((counter + 1)) # 这在子 shell 中!
done
echo "counter=$counter" # 输出 0,而非 5
# 解决方案 1:使用进程替换
while read n; do
counter=$((counter + 1))
done < <(seq 1 5)
echo "counter=$counter" # 输出 5
# 解决方案 2:使用 here-string
while read n; do
counter=$((counter + 1))
done <<< "$(seq 1 5)"
五、实用范例合集
5.1 系统运维
bash
# 查找占用 CPU 最高的 5 个进程
ps aux --sort=-%cpu | head -6
# 查找大目录
du -sh /* 2>/dev/null | sort -h | tail -10
# 查看当前连接数最多的 IP
netstat -ant | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
# 查找最近修改的文件
find . -type f -printf '%T@ %p\n' | sort -rn | head -10 | cut -d' ' -f2-
# 查看各文件系统使用情况
df -h | grep -v "^tmpfs\|^devtmpfs"
5.2 日志分析
bash
# 统计 HTTP 状态码分布
cat access.log | awk '{print $9}' | sort | uniq -c | sort -rn
# 查找 404 最多的 URL
grep " 404 " access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
# 统计每小时请求量
cat access.log | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn
# 提取慢请求(响应时间 > 1 秒)
awk '$NF > 1.0' access.log | tail -20
5.3 数据转换
bash
# CSV 转 JSON(简单版)
cat data.csv | awk -F, 'NR==1{for(i=1;i<=NF;i++)h[i]=$i;next}
{printf "{"; for(i=1;i<=NF;i++) printf "\"%s\":\"%s\"%s",h[i],$i,(i==NF?"":","); print "}"}'
# 进制转换管道
echo "FF" | tr '[:lower:]' '[:upper:]' | xargs -I {} bash -c 'echo "obase=10; ibase=16; {}" | bc'
# URL 编码/解码
echo "hello world" | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))"
5.4 开发相关
bash
# 统计项目代码行数(排除空行和注释)
find . -name "*.go" | xargs cat | grep -v "^$" | grep -v "^\s*//" | wc -l
# 列出方法定义
grep -rn "^func " --include="*.go" . | awk -F: '{print $1":"$2" "$3}'
# 按作者统计 Git 提交
git log --format='%an' | sort | uniq -c | sort -rn
# 查找 Git 提交信息中的关键词
git log --oneline | grep -i "fix\|bug\|hotfix"
5.5 网络相关
bash
# 检测端口是否开放
echo "quit" | timeout 3 telnet example.com 80 2>&1 | grep "Connected"
# 提取域名对应的 IP
nslookup example.com 2>/dev/null | grep "Address" | tail -1 | awk '{print $2}'
# 批量 ping
echo "8.8.8.8 1.1.1.1 8.8.4.4" | xargs -n 1 ping -c 1 -W 2
# 生成 SSH 密钥并直接复制到远程
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" && cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
六、性能考量与限制
6.1 管道缓冲区
管道的缓冲区大小有限制,Linux 上通常为 64KB(可由 ulimit -p 查看):
bash
# 产生大量数据时写入方会被阻塞
yes "data" | (sleep 1; head -1) # yes 会因缓冲区满而暂停
6.2 注意事项
-
避免无用的 cat(UUOC --- Useless Use of Cat):
bash# 不推荐 cat file.txt | grep "keyword" # 推荐 grep "keyword" file.txt -
管道中的命令无法交互 :管道是非交互式的,需要交互的命令(如
less、vim)在管道中间没有意义。 -
错误处理:管道只传递 stdout,stderr 不会被传递。如需传递 stderr:
bashcommand 2>&1 | grep "error" -
管道中的 exit 行为 :子 shell 中执行
exit不会退出整个脚本,需要使用set -o pipefail和PIPESTATUS来检查失败。 -
二进制数据处理 :管道按字节流传输,不关心内容是否为文本,但某些文本处理工具(如
grep)在遇到二进制数据时可能产生意外结果。
七、管道与其他重定向的对比
| 特性 | 管道 | | 输入重定向 < | 输出重定向 > |
|------|----------------|--------------|-------------|
| 连接方式 | 命令 → 命令 | 文件 → 命令 | 命令 → 文件 |
| 数据流向 | stdin ↔ stdout | 文件内容 → stdin | stdout → 文件 |
| 并发性 | 两端并发 | N/A | N/A |
| 中间文件 | 不需要 | 需要文件存在 | 创建/覆盖文件 |
组合使用
bash
# 从文件读取 → 管道处理 → 写入另一个文件
< input.txt sort | uniq -c | sort -rn > output.txt
# 多个输入源的合并处理
sort <(cat file1.txt) <(cat file2.txt) | uniq
# 错误重定向到文件,同时管道正常输出
command 2>error.log | grep "keyword" > result.txt
八、总结
管道是 Linux 命令行的灵魂特性,掌握它可以极大提升工作效率。核心要点:
|串联命令,让数据像流水线一样传递grep、sort、uniq、wc、awk、sed是管道中最常用的处理工具xargs将管道输出转为命令行参数tee在管道中分流输出- 命名管道(FIFO) 实现不相关进程间的通信
- 进程替换
<( )让命令输出像文件一样使用 - 注意管道中的子 shell 问题,变量修改不会影响外部作用域
管道的强大在于可组合性------一旦掌握了这些基本构件,你可以像搭积木一样创建出强大而灵活的数据处理流水线。