Linux 命令初识第 4 课:管道与重定向
目标:会用
>>>2>把输出存进文件,会用|把几个小命令串成一条流水线,会用sortuniqcut做日志统计,分清;&&||。 用时:约 40 分钟。全部在 Ubuntu 里敲。
这一课解决两个问题:
| 问题 | 工具 | 例子 |
|---|---|---|
| 命令的输出不想只看一眼,想存下来 | 重定向 > >> 2> | 把报错日志存成文件发给同事 |
| 一个命令做不完,想让几个命令接力 | 管道 | | 「访问量最高的 3 个 IP 是谁」 |
0. 准备练习数据
把下面整段复制到 Ubuntu 里执行,会在 ~/practice/lesson4 下生成一份 100 行的接口访问日志:
mkdir -p ~/practice/lesson4 && cd ~/practice/lesson4
for i in $(seq 1 100); do
if (( i % 20 < 10 )); then ip="10.0.0.1"
elif (( i % 20 < 16 )); then ip="10.0.0.2"
elif (( i % 20 < 19 )); then ip="10.0.0.3"
else ip="10.0.0.4"
fi
case $(( i % 3 )) in
0) path="/api/order" ;;
1) path="/api/user" ;;
2) path="/api/pay" ;;
esac
if (( i % 25 == 0 )); then code=500
elif (( i % 9 == 0 )); then code=404
else code=200
fi
echo "$ip GET $path $code"
done > access.log
执行完 head access.log 看一眼,每行 4 个字段,用空格隔开:
10.0.0.1 GET /api/user 200
↑ 客户端IP ↑方法 ↑ 接口路径 ↑ 状态码
脚本里的
for、if是 shell 脚本语法,现在照抄即可;最后那个done > access.log就是本课要讲的重定向——把整个循环的输出存进文件。
1. 先搞懂:命令有三个「口」

每个命令运行时,Linux 都给它准备了三个通道:
┌──────────┐ ──→ 标准输出 stdout (编号 1):正常结果
标准输入 ──→ │ 命令 │
stdin (0) └──────────┘ ──→ 标准错误 stderr (编号 2):报错信息
| 通道 | 编号 | 默认去哪 | 例子 |
|---|---|---|---|
| 标准输入 stdin | 0 | 键盘 | cat 不带文件名时等你打字 |
| 标准输出 stdout | 1 | 屏幕 | ls 列出的文件名 |
| 标准错误 stderr | 2 | 屏幕 | No such file or directory |
stdout 和 stderr 默认都打到屏幕上,所以你平时分不出来。重定向的本质就是:改变这些通道的去向。
2. 重定向 —— 把输出存进文件
| 写法 | 作用 | 例子 |
|---|---|---|
> 文件 | stdout 写入文件,覆盖原内容(文件不存在就新建) | ls > files.txt |
>> 文件 | stdout 追加到文件末尾 | echo "done" >> run.log |
2> 文件 | stderr 写入文件 | ls nofile 2> err.txt |
> 文件 2>&1 | stdout 和 stderr 都写进同一个文件 | ./start.sh > app.log 2>&1 |
&> 文件 | 同上的简写(bash 专用) | ./start.sh &> app.log |
2> /dev/null | 把报错丢掉 | find / -name "nginx.conf" 2>/dev/null |
< 文件 | 从文件读入,代替键盘(了解即可) | wc -l < access.log |
几个要点
-
>会直接清空文件,不提示。>写错成覆盖重要文件,内容就没了。拿不准时用>>。 -
/dev/null是 Linux 的「黑洞」,写进去的东西全部消失。第 3 课说的「find /刷屏报Permission denied」,加2>/dev/null就清净了。 -
2>&1读作「把 2 号通道指向 1 号通道现在去的地方」,所以必须写在> 文件后面。后端启动服务时经常这样写:nohup ./server > server.log 2>&1 & # 正常日志和报错都进 server.log,& 放到后台(第 6 课讲)
3. 管道 | —— 让命令接力

命令A | 命令B | 命令C
把前一个命令的 stdout,当作后一个命令的 stdin。每个命令只做一件小事,串起来就能完成复杂的统计。
grep "404" access.log | wc -l # 先筛出 404 的行,再数行数
find ~/practice -name "*.go" | wc -l # 数一数有多少个 go 文件
ls -l /etc | less # 输出太长,交给 less 翻页看
⚠️ 管道只传 stdout,不传 stderr。所以报错信息照样会打到屏幕上,不会进入下一个命令。
管道里的常用「零件」
| 命令 | 作用 | 例子 |
|---|---|---|
wc -l | 数行数 | ... | wc -l |
head -n N / tail -n N | 取前 / 后 N 行 | ... | head -n 3 |
sort | 排序(按字符) | sort access.log |
sort -n / sort -rn | 按数字排序 / 数字倒序 | ... | sort -rn |
uniq | 去掉相邻的重复行 | ... | sort | uniq |
uniq -c | 去重并在行首标出重复次数 | ... | sort | uniq -c |
cut -d ' ' -f N | 按分隔符切开,取第 N 列 | cut -d ' ' -f 1 access.log |
tee 文件 | 一边显示到屏幕,一边存进文件 | ... | tee result.txt |
cut —— 按列切

日志、配置、CSV 都是「一行一条记录,字段之间用某个符号隔开」。cut 就是把每一行按分隔符切开,只留你要的那几列。
cut -d '分隔符' -f 第几列 文件
cut -d ' ' -f 1 access.log
拿 access.log 的第一行举例,按空格切开后从 1 开始编号:
10.0.0.1 GET /api/user 200
↑ ↑ ↑ ↑
-f 1 -f 2 -f 3 -f 4
| 写法 | 含义 | 对第一行的结果 |
|---|---|---|
-f 1 | 第 1 列 | 10.0.0.1 |
-f 1,4 | 第 1 列和第 4 列 | 10.0.0.1 200 |
-f 2-3 | 第 2 到第 3 列 | GET /api/user |
-f 3- | 第 3 列到最后 | /api/user 200 |
取多列时,输出的列之间还是用原来的分隔符连起来。
分隔符换一下,就能切别的文件
cut -d ' ' -f 1,4 access.log # 日志:IP 和状态码
cut -d ':' -f 1,7 /etc/passwd # 系统用户表:用户名和登录 shell,如 root:/bin/bash
cut -d ',' -f 2 users.csv # CSV:第 2 列(比如导出的用户手机号)
cut -d '=' -f 2 .env # 配置:取等号右边的值
/etc/passwd 每行长这样,字段用 : 隔开,可以 head -n 3 /etc/passwd 自己看一眼:
root:x:0:0:root:/root:/bin/bash
用户名:密码占位:UID:GID:说明:家目录:登录shell
三个坑
-
不写
-d时,默认分隔符是 Tab,不是空格。对空格分隔的文件直接cut -f 1,会把整行原样输出,看起来像「没切」。 -
连续多个空格会切出空列。
cut把每一个空格都当成一次分隔,两个空格中间就是一个空字段:echo "a b" | cut -d ' ' -f 2 # 输出空行(第 2 列是两个空格中间的「空」)echo "a b" | cut -d ' ' -f 3 # 输出 bls -l、ps这类为了对齐会填充多个空格的输出,用cut切就容易错位,这时改用awk '{print $1}'(了解即可,后面遇到再学)。 -
-d只能是一个字符,-d ', '这种两个字符的写法会报错。
了解即可:
cut -c 1-10按字符位置切,比如只取日志每行的前 10 个字符(日期部分)。
⚠️ 坑:uniq 只认「挨着的」重复
a uniq 之后 a
a ───────→ b
b a ← 第二个 a 没被去掉,因为它和第一个 a 不挨着
a
所以 uniq 前面几乎总是先 sort,让相同的行排到一起。
后端最经典的一条:访问量排行
cut -d ' ' -f 1 access.log | sort | uniq -c | sort -rn | head -n 3
拆开看每一步的输出:
| 步骤 | 做了什么 | 输出长什么样 |
|---|---|---|
cut -d ' ' -f 1 access.log | 每行只留第 1 列(IP) | 10.0.0.1 10.0.0.1 10.0.0.2 …… |
| sort | 排序,相同 IP 挨在一起 | 10.0.0.1 × N 行,然后 10.0.0.2 …… |
| uniq -c | 合并并计数 | 50 10.0.0.1 |
| sort -rn | 按次数从大到小排 | 次数最多的在最上面 |
| head -n 3 | 只要前 3 名 | 3 行 |
学管道的方法:一节一节往上加,每加一个
|就执行一次看输出,不要一口气写完。 了解即可:工作中切列更常用awk '{print $1}',它能自动处理多个连续空格,后面遇到再学。
4. 命令的连接:; && ||

| 写法 | 含义 | 例子 |
|---|---|---|
A ; B | A 执行完就执行 B,不管 A 成没成功 | cd /tmp ; ls |
A && B | A 成功了才执行 B | mkdir demo && cd demo |
A || B | A 失败了才执行 B | cd nodir || echo "目录不存在" |
A & | 把 A 放到后台运行(不是「并且」!第 6 课讲) | — |
怎么判断「成功」?每个命令结束时会返回一个退出码:0 表示成功,非 0 表示失败。用 echo $? 查看上一条命令的退出码:
ls /tmp ; echo $? # 0
ls /nodir ; echo $? # 2(非 0,失败)
后端常见写法:
go build && ./app # 编译成功才运行,编译失败就别跑旧的了
cd /data/app && rm -rf ./tmp/* # cd 失败就不删!用 ; 的话会在当前目录删,非常危险
上面第二行是
&&最重要的用途:前一步失败时,后面的危险操作不能执行。
5. 跟着敲一遍
cd ~/practice/lesson4
echo "first" > note.txt
echo "second" > note.txt
cat note.txt # 猜:几行?
echo "third" >> note.txt
cat note.txt # 猜:几行?
ls access.log nofile # 猜:哪部分是 stdout,哪部分是 stderr?
ls access.log nofile > out.txt # 猜:屏幕上还剩什么?
cat out.txt
ls access.log nofile > out.txt 2>&1
cat out.txt
ls nofile 2>/dev/null ; echo $? # 猜:屏幕上输出什么?
cut -d ' ' -f 4 access.log | head -n 5
cut -d ' ' -f 1,4 access.log | head -n 3
cut -d ' ' -f 3- access.log | head -n 3
cut -f 1 access.log | head -n 3 # 猜:为什么整行都还在?
cut -d ':' -f 1,7 /etc/passwd | head -n 3
cut -d ' ' -f 4 access.log | uniq | head -n 5
cut -d ' ' -f 4 access.log | sort | uniq -c
cd nodir && echo "进来了"
cd nodir ; echo "进来了"
每敲一条,先猜结果,再看输出对不对。
6. 练习题
题目在本文件夹的 题目解答.md 里,把每题的命令、输出和回答写在 解答: 下面,做完告诉我,我来批改。
7. 课后
- 提交:
01-环境准备-01-linux命令初识-第4课管道与重定向