跳到主要内容

Linux 命令初识第 4 课:管道与重定向

目标:会用 > >> 2> 把输出存进文件,会用 | 把几个小命令串成一条流水线,会用 sort uniq cut 做日志统计,分清 ; && ||。 用时:约 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. 先搞懂:命令有三个「口」​

三通道与重定向:stdin、stdout、stderr、覆盖追加和合并输出

每个命令运行时,Linux 都给它准备了三个通道:

┌──────────┐ ──→ 标准输出 stdout (编号 1):正常结果
标准输入 ──→ │ 命令 │
stdin (0) └──────────┘ ──→ 标准错误 stderr (编号 2):报错信息
通道编号默认去哪例子
标准输入 stdin0键盘cat 不带文件名时等你打字
标准输出 stdout1屏幕ls 列出的文件名
标准错误 stderr2屏幕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>&1stdout 和 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

几个要点​

  1. > 会直接清空文件,不提示。> 写错成覆盖重要文件,内容就没了。拿不准时用 >>。

  2. /dev/null 是 Linux 的「黑洞」,写进去的东西全部消失。第 3 课说的「find / 刷屏报 Permission denied」,加 2>/dev/null 就清净了。

  3. 2>&1 读作「把 2 号通道指向 1 号通道现在去的地方」,所以必须写在 > 文件 后面。后端启动服务时经常这样写:

    nohup ./server > server.log 2>&1 & # 正常日志和报错都进 server.log,& 放到后台(第 6 课讲)

3. 管道 | —— 让命令接力​

管道与 tee:标准输出接力、错误去向和边看边存

命令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 —— 按列切​

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

三个坑​

  1. 不写 -d 时,默认分隔符是 Tab,不是空格。对空格分隔的文件直接 cut -f 1,会把整行原样输出,看起来像「没切」。

  2. 连续多个空格会切出空列。cut 把每一个空格都当成一次分隔,两个空格中间就是一个空字段:

    echo "a b" | cut -d ' ' -f 2 # 输出空行(第 2 列是两个空格中间的「空」)
    echo "a b" | cut -d ' ' -f 3 # 输出 b

    ls -l、ps 这类为了对齐会填充多个空格的输出,用 cut 切就容易错位,这时改用 awk '{print $1}'(了解即可,后面遇到再学)。

  3. -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 ; BA 执行完就执行 B,不管 A 成没成功cd /tmp ; ls
A && BA 成功了才执行 Bmkdir demo && cd demo
A || BA 失败了才执行 Bcd 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课管道与重定向