Skip to content

Linux 常用命令与部署 ​

S3-011必学入门约 90 分钟JDK 21企业开发基础 · Linux 与 Shell

1. 本节定位 ​

你写的代码最终要跑在服务器上,而服务器基本都是 Linux,而且没有图形界面 ✓

text
本地:    IDEA 里点按钮 → 编译、运行、看日志
服务器:  ssh 登录 → 命令行 → 看日志、改配置、排查问题

所以后端工程师必须能在"只有命令行"的环境里工作 ✓

本节的第一个认知:

命令行不是"老古董",它是"可编程、可组合、可远程"的操作方式 ✓

图形界面能做的事有限,命令行能做的事由你组合决定 ✓

2. 学习目标 ​

完成本节后,你应该能够:

  • 说出 Linux 目录结构的关键路径(/、~、/etc、/var/log、/opt);
  • 熟练使用文件与目录操作(ls / cd / pwd / mkdir / cp / mv / rm);
  • 用 tail -f 看实时日志 ★(这是最常用的一条);
  • 用 find / grep 定位文件与内容,并知道排除构建产物 ✓;
  • 理解"管道"与"重定向",能把多个小命令组合起来 ★★★;
  • 说出 grep / sed / awk 的分工;
  • 会用 tar 打包解压;
  • 理解 Unix 哲学:小工具 + 组合 ✓。

3. 前置知识检查 ​

  • 知道"文件系统、路径、进程"这些概念吗?
  • 用过终端(哪怕只是 cd / ls)吗?

4. 为什么需要这个知识 ​

先看三个只有命令行能高效完成的场景:

场景图形界面命令行
在 2 万个文件里找出"哪些文件包含某个配置"逐个打开 ✗grep -rl "xxx" . 一秒 ✓
看服务"现在正在打印什么"下载日志文件再看 ✗tail -f app.log ✓ 实时
统计日志里"今天有多少 500 错误"导入 Excel ✗grep -c " 500 " app.log ✓
批量改名 / 批量替换手工 ✗一行脚本 ✓

更根本的一点:服务器上的很多操作只能通过命令行完成(没有桌面、只有 SSH)✓

5. 核心概念 ​

5.1 为什么是 Linux ​

理由说明
免费与开源无授权成本 ✓
稳定服务器常年不重启 ✓
生态绝大多数服务端软件(Nginx、Redis、Kafka、Docker、K8s)首选/仅支持 Linux ✓
可远程、可脚本化运维自动化的基础 ✓

Java 的"一次编译到处运行"在这里的意义:

同一个 jar,可以在 Windows 开发、在 Linux 运行 ✓ ——但"运行起来之后怎么管它"是 Linux 的技能 ✓

5.2 路径与目录结构 ​

四个必须记住的路径符号:

符号含义
/根目录(Linux 一切从 / 开始,没有 C 盘 D 盘)✓
~当前用户的家目录(如 /home/deploy)✓
.当前目录
..上级目录

绝对路径 vs 相对路径:

bash
/opt/app/logs/app.log      # 绝对路径:从 / 开始 ✓
logs/app.log               # 相对路径:从当前目录开始
./start.sh                 # 明确表示"当前目录下的 start.sh"
../config/app.yml          # 上级目录的 config 下

常用目录(记住这张表,登录服务器后不会迷路):

目录放什么
/etc配置文件(如 /etc/nginx/nginx.conf)★
/var/log日志(如 /var/log/nginx/access.log)★
/opt第三方软件 / 自己的应用(很多团队把应用放这里)★
/home/<user>用户的家目录
/tmp临时文件(可能被清理 ⚠️)
/usr/bin系统命令
/proc内核与进程的虚拟文件系统(cat /proc/<pid>/status 能看进程状态)★

⚠️ 一个新手常犯的错误:把应用装在 /tmp 或用户的家目录 ✗ ——应该用 /opt 或 /srv,并且有明确的部署用户 ✓

5.3 文件与目录操作 ​

bash
pwd                        # 我在哪
ls -la                     # 列出(含隐藏文件、详细权限)★
cd /opt/app                # 切换目录
mkdir -p logs/archive      # 建目录(-p:父目录不存在就一起建)★
cp -r conf conf.bak        # 复制目录(-r)
mv app.log app.log.1       # 改名 / 移动
rm -rf old/                # 删除(-r 递归、-f 强制)⚠️ **危险**

ls -l 的输出(实测):

text
-rw-r--r-- 1 testuser 1000 6494 Sep 21 16:26 README.md
-rw-r--r-- 1 testuser 1000 2555 Sep 21 15:32 labs/pom.xml
字段含义
-rw-r--r--权限(详见 《Linux 文件、用户与权限管理》)
6494字节大小
Sep 21 16:26修改时间

⚠️ rm -rf 的三条保命经验:

经验说明
rm -rf 前先 ls 一遍确认路径没写错 ✗(写成 rm -rf /opt/app/ vs rm -rf / opt/app/ —— 一个空格的差别 ✗✗)
变量要用引号rm -rf "$DIR"/* ——如果 $DIR 为空,就变成 rm -rf /* ✗✗✗
重要数据先备份或者至少用"移动到一个待删目录"代替直接删 ✓

5.4 查看文件 ​

命令用途典型
cat全部输出小文件 ✓
less ★分页查看(可搜索、可上下翻)大文件首选 ✓
head -n 20看开头看文件结构 ✓
tail -n 100 ★看结尾看最新日志 ✓
tail -f ★★★实时跟踪看服务现在在打什么 ✓✓
`tail -fgrep ERROR`只看错误 ★★

tail -f 为什么是"后端第一命令":

线上出问题时,你第一个要做的往往是"看看它现在在打什么日志" ✓

less 用于"翻历史日志",tail -f 用于"看当下" ✓

less 的常用键(记住这几个就够):

键作用
/关键词向下搜索 ★
n / N下一个 / 上一个匹配 ✓
G / g跳到末尾 / 开头 ✓
q退出

5.5 查找:find 与 grep ​

这是本节最有价值的一组命令 ✓

find:按条件找文件 ​

bash
find . -name "*.log"                    # 按名字
find /var/log -type f -mtime -1         # 1 天内修改过的文件 ★
find . -size +100M                      # 大于 100MB 的文件 ★
find . -name "*.log" -exec rm {} \;     # 找到并删除 ⚠️

grep:在内容里找 ​

bash
grep "ERROR" app.log                    # 找包含 ERROR 的行
grep -n "ERROR" app.log                 # 带行号 ★
grep -r "config" /etc/nginx/            # 递归目录 ★
grep -rl "reflog" docs                  # ★ 只列出**文件名**(-l)
grep -c " 500 " access.log              # 计数 ★
grep -v "DEBUG" app.log                 # 反向(排除)★
grep -o "v2\.[0-9]\.[0-9]-[a-z-]*" README.md    # 只输出匹配的部分 ★

实测(一):grep -rl 的结果

text
$ grep -rl "reflog" docs
docs/.vitepress/dist/00-start/tools/S0-004.html
docs/.vitepress/dist/03-enterprise/engineering/S3-030.html
docs/.vitepress/dist/assets/00-start_tools_S0-004.md.CJO1a9ot.js
docs/.vitepress/dist/assets/03-enterprise_engineering_S3-030.md.z43-M84M.js
docs/.vitepress/dist/assets/03-enterprise_engineering_S3-030.md.z43-M84M.lean.js

⚠️ 注意:前 5 个结果全是构建产物(docs/.vitepress/dist/)——它们只是"打包时把源码复制了进去",不是真正的源文件 ✗

排查时搜到构建产物,会浪费大量时间(在几百个 .js 里找"哪个才是真的")✗

实测(二):排除之后

text
$ grep -rl "reflog" docs --exclude-dir=dist
docs/00-start/tools/S0-004.md
docs/03-enterprise/engineering/S3-030.md

★ 只剩 2 个真正的源文件 ✓✓

这个对比说明了什么:

grep 默认会搜所有文件——包括构建产物、依赖目录(node_modules)、打包输出 ⚠️

所以搜索时"排除"是必备习惯,不是可选优化 ✓

常用的排除参数:

参数作用
--exclude-dir=dist ★排除目录(可多次指定)✓
--exclude="*.min.js"排除文件模式 ✓
--exclude-dir={dist,target,node_modules}一次排除多个(bash 的花括号展开)★

实测(三):grep -o + sort -u 提取并去重

text
$ grep -o "v2[.][0-9][.][0-9]-[a-z-]*" README.md | sort -u
v2.0.0-ta
v2.1.0-data-access
v2.2.0-spring-boot-engineering
v2.3.0-mysql-performance
v2.4.0-web-basics
v2.5.0-engineering

★ 这条命令做了三件事:

text
grep -o   → 只输出"匹配到的那一段"(而不是整行)✓
sort -u   → 排序并去重 ✓
管道 |    → 把前一个的输出当后一个的输入 ✓

这就是"组合"的力量:两个小工具接起来,完成了"从文档里提取所有版本号"这件没有单个命令能做的事 ✓✓

5.6 管道与重定向 ​

这是本节的核心 ★★★

管道 |:把上一个命令的输出,变成下一个命令的输入 ​

bash
cat app.log | grep ERROR | wc -l            # 数一数有多少错误
ps -ef | grep java                          # 找 java 进程 ★
ls -l | sort -k5 -n                         # 按大小排序

实测(组合出"最长的讲义"):

text
$ wc -l docs/03-enterprise/web/*.md | sort -n | tail -6
   948 docs/03-enterprise/web/S3-018.md
  1156 docs/03-enterprise/web/S3-021.md
  1160 docs/03-enterprise/web/S3-020.md
  1186 docs/03-enterprise/web/S3-019.md
  4450 total

三步拆解:

步骤命令作用
①wc -l *.md统计每个文件的行数
②sort -n按数字排序(不是字典序)✓
③tail -6取最后 6 行(= 最大的几个)✓

重定向:把输出写到文件 ​

符号作用
>覆盖写入 ⚠️(会清空原文件)
>> ★追加写入(保留原内容)✓
2>只写错误输出
2>&1 ★把错误也合并到标准输出
<从文件读输入
/dev/null丢弃输出("黑洞")✓

几个典型用法:

bash
./start.sh > app.log 2>&1 &                # ★ 后台启动,日志全部写进 app.log
echo "export JAVA_HOME=..." >> ~/.bashrc   # ★ 追加(用 > 会毁掉整个配置文件 ✗✗)
grep ERROR app.log > errors.txt            # 错误行存下来
command > /dev/null 2>&1                   # 完全静默

⚠️ > 与 >> 用错的后果(这是真实事故):

bash
echo "xxx" > ~/.bashrc        # ✗ 你的 shell 配置被清空了
echo "xxx" >> ~/.bashrc       # ✓

⚠️ 2>&1 的一个常见误解:顺序很重要 ✓

bash
command > file 2>&1     # ✓ 正确:先把 stdout 指向 file,再把 stderr 指向"stdout 当前的位置"(也是 file)
command 2>&1 > file     # ✗ 错误:先把 stderr 指向"当时的 stdout"(终端),再把 stdout 指向 file
                        #   结果:错误还是打在屏幕上 ✗

关于"退出码"(这是脚本与 CI 的基础):

bash
$ ls not-exist; echo "退出码=$?"
退出码=2
退出码含义
0成功 ✓
非 0失败(1 = 一般错误、2 = 用法错误、127 = 命令不存在)✓

$? 是"上一个命令的退出码" ★ ——这就是脚本能做"if 判断"与 CI 能判断成败的依据 ✓

5.7 文本处理三剑客 ​

工具定位典型
grep筛选行grep ERROR app.log ✓
sed按行编辑(替换、删除)sed 's/old/new/g' file ✅ 批量替换 ★
awk按列处理 / 统计 ★awk '{print $1}' access.log 取第一列

awk 的一个实用例子(统计访问日志的状态码分布):

bash
awk '{print $9}' access.log | sort | uniq -c | sort -rn | head

拆解:awk 取第 9 列 → sort 排序 → uniq -c 计数 → sort -rn 按数字倒序 → head 取前几 ✓

★ 又是"组合" ——每个工具只做一件小事 ✓

5.8 压缩与打包 ​

bash
tar -czf app.tar.gz app/          # 打包并 gzip 压缩 ★
tar -xzf app.tar.gz               # 解压 ★
tar -tzf app.tar.gz               # ★ 只看内容不解压(确认包里有什么,很重要 ✓)
tar -czf logs-$(date +%F).tar.gz logs/     # 带日期的归档名 ★

参数记忆:c 创建、x 解压、t 查看、z gzip、f 指定文件名 ✓

⚠️ 两个实用提醒:

提醒说明
解压前先 tar -tzf 看一眼避免"解压出一堆散文件"污染目录 ✗
打包时不要包含绝对路径tar -czf x.tar.gz /opt/app 解压时会覆盖原路径 ⚠️ ——用 -C 先切目录 ✓

5.9 Unix 哲学 ​

本节的思想内核 ★★★:

① 每个程序只做好一件事② 程序之间用"文本流"连接(这就是为什么日志是文本,而不是二进制) ③ 用管道组合出你想做的事

对比:

"大而全"的工具Unix 小工具组合
学习成本要学一个庞大的软件每个命令学 5 分钟 ✓
能做的事软件提供什么就做什么 ✗组合决定 ✓
可复用弱强(同一个 sort 到处用)✓

一个具体的体现(本节的实测):

text
"找出最长的 5 篇讲义"  →  wc -l | sort -n | tail -6
"提取所有版本号"        →  grep -o | sort -u
"统计状态码分布"        →  awk | sort | uniq -c | sort -rn | head

没有一个命令是"用来做这些事"的——但组合起来就能做 ✓✓

6. 递进示例 ​

6.1 最小示例:看日志 ​

bash
tail -f /opt/app/logs/app.log

6.2 业务示例:一次"错误排查" ​

bash
# ① 看服务现在在打什么
tail -f app.log | grep -i error

# ② 统计今天有多少 500
grep "$(date '+%d/%b/%Y')" access.log | grep -c ' 500 '

# ③ 找出最慢的 10 个请求(假设日志最后一列是耗时)
awk '{print $NF, $7}' access.log | sort -rn | head -10

# ④ 把相关的日志单独存一份,便于后续分析
grep 'ERROR' app.log > /tmp/errors-$(date +%F).txt

6.3 反例:把日志下载到本地用 Excel 分析 ​

为什么不好:

问题说明
日志很大(GB 级)下载慢、本地打不开 ✗
可能含敏感信息下载到本机有合规风险 ⚠️
慢且重复每次都要重新下载 ✗

正确做法:在服务器上用命令行分析(grep/awk/sort),只把"结论"(几行)带回来 ✓

7. 完整可运行命令 ​

⚠️ 关于本节的环境:本机是 Windows,演示用的是 Git Bash(MinGW) ✓

它能演示:文件系统操作、文本处理、管道、脚本(本节绝大部分内容)✓ 它不能演示:systemd、apt、/proc 的真实行为 —— 这些在 《Linux 服务部署与运维》 里以概念方式讲解,并明确标注 ✓

运行方式(Windows 上):

powershell
& "C:\Program Files\Git\bin\bash.exe" -c 'cd <仓库根目录>; <你要跑的命令>'

(注意用单引号包裹,避免 PowerShell 抢走 $、<、> 等符号 ⚠️)

实测(一):基本信息与统计

bash
pwd
find docs -name '*.md' | wc -l
wc -l docs/03-enterprise/web/*.md | sort -n | tail -6
text
<仓库根目录>
93
   948 docs/03-enterprise/web/S3-018.md
  1156 docs/03-enterprise/web/S3-021.md
  1160 docs/03-enterprise/web/S3-020.md
  1186 docs/03-enterprise/web/S3-019.md
  4450 total

实测(二):grep 的"排除构建产物"效果 ★

bash
grep -rl "reflog" docs | head -5                        # 不排除
grep -rl "reflog" docs --exclude-dir=dist | head -5      # 排除后
text
不排除:
docs/.vitepress/dist/00-start/tools/S0-004.html
docs/.vitepress/dist/03-enterprise/engineering/S3-030.html
docs/.vitepress/dist/assets/00-start_tools_S0-004.md.CJO1a9ot.js
...

排除后:
docs/00-start/tools/S0-004.md
docs/03-enterprise/engineering/S3-030.md

实测(三):grep -o + sort -u

bash
grep -o "v2[.][0-9][.][0-9]-[a-z-]*" README.md | sort -u
text
v2.0.0-ta
v2.1.0-data-access
v2.2.0-spring-boot-engineering
v2.3.0-mysql-performance
v2.4.0-web-basics
v2.5.0-engineering

实测(四):退出码

bash
ls not-exist; echo "退出码=$?"
text
退出码=2

四处实测的结论:

实测结论
**`findwc -l` = 93**
排除 dist 后从 5 个变 2 个 ★搜索时排除构建产物是必备习惯
**`grep -osort -u` 提取出 6 个版本号** ★
退出码=2命令的成败是可判定的(脚本与 CI 的基础)✓

8. Debug 观察点 ​

  1. 把 sort -n 换成 sort:观察 100 排在 20 前面(字典序 vs 数值序)✗ ——理解 -n 为什么重要 ✓;
  2. grep -rl 不加 --exclude-dir:观察构建产物混进来(体会"排除"的价值)✓;
  3. 把 > 误用成覆盖:在一个临时文件上试 echo a > f.txt; echo b > f.txt,观察只剩 b ✗ ——然后换成 >> ✓;
  4. 2>&1 > file 与 > file 2>&1 对比:观察错误输出去哪了 ✓;
  5. 看退出码:ls /; echo $? 与 ls /not-exist; echo $? 对比 ✓。

9. 常见错误 ​

错误 1:rm -rf 写错路径 ​

见 5.3 节。这是最危险的命令 ✗

特别提醒:变量没加引号 是隐蔽杀手 ✓

bash
rm -rf $DIR/*        # ✗ DIR 为空 → rm -rf /*
rm -rf "$DIR"/*      # ✓ 至少在 DIR 为空时变成 "rm -rf /*" 但仍危险 —— 更好的是先判断
[ -n "$DIR" ] && rm -rf "$DIR"/*

错误 2:搜索时不排除构建产物 ​

见 5.5、7(二)。浪费时间且容易改错文件 ✗

错误 3:用 > 覆盖重要配置文件 ​

见 5.6 节。>> 是追加,> 是覆盖 ✓

错误 4:grep 时不加引号 ​

bash
grep ERROR app.log        # 简单场景 OK
grep "500 Internal" app.log    # ★ 有空格必须加引号,否则被当成两个参数

错误 5:以为 | 会传递"失败" ​

bash
command1 | command2        # 默认:command2 的退出码才是整体的退出码
# 想让任一环节失败就整体失败 → set -o pipefail(见 S3-035)✓

错误 6:在 Windows 的 PowerShell 里直接写 Linux 命令 ​

powershell
ls -la | head -5      # ✗ PowerShell 的 ls 是别名,head 不存在

修:用 Git Bash(本节的方式)或 WSL ✓

错误 7:把 sudo 当"万能前缀" ​

bash
sudo chmod 777 /opt/app     # ✗ 777 是"所有人可读写执行",很危险 ✗✗

正确做法:最小权限(《Linux 文件、用户与权限管理》)✓

10. 动手实验 ​

目标:把"查找、管道、重定向"练熟。

步骤:

  1. 按第 7 节跑四条命令,核对结果;
  2. 组合出自己的命令:统计本项目里"所有讲义的总行数"(提示:find ... | xargs wc -l 或 find -name '*.md' -exec wc -l {} +)✓;
  3. 验证排除:grep -rl "Spring" docs vs grep -rl "Spring" docs --exclude-dir=dist,对比结果数量 ✓;
  4. 练习重定向:echo a > /tmp/t.txt; echo b >> /tmp/t.txt; cat /tmp/t.txt(观察 > 与 >> 的差别)✓;
  5. 练习 awk:ls -l | awk '{print $5, $9}'(取"大小"与"文件名"两列)✓;
  6. 练习退出码:跑几个成功/失败的命令,观察 $? ✓。

验收方式:能说出第 3 步两个结果的差距、第 4 步 > 与 >> 的差别。

扩展任务:写一个"日志分析"命令组合:

  • 目标:从一份 Nginx/应用访问日志里,统计"最慢的 10 个请求";
  • 用 awk 取耗时列、sort -rn、head;
  • 说明:每一步在做什么,以及为什么这种"组合"比"写个 Python 脚本"更合适(提示:快、无需安装、可一行放进运维手册)✓

11. 练习题 ​

基础题

  1. 判断:grep -r 默认不会搜索构建产物目录。(对 / 错)
  2. 填空:> 表示 ______,>> 表示 ______;把标准错误合并到标准输出用 ______。
  3. 简答:为什么"每个程序只做好一件事,用管道组合"这种设计是好的?

代码阅读题

bash
cat app.log | grep "ERROR" | awk '{print $2}' | sort | uniq -c | sort -rn | head -5
  1. 这条命令做了什么?请逐步拆解。
  2. 如果 app.log 有 2GB,这条命令有什么问题?怎么改进?

编程题

  1. 写一组命令完成"日志归档":
    • 需求:把 7 天前的日志打包、压缩、归档后删除原文件、并输出归档报告;
    • 要求:
      • 用 find -mtime +7 找旧日志;
      • 用 tar -czf 打包(文件名带日期);
      • 删除前先确认打包成功(提示:检查 tar 的退出码)★;
      • 输出报告(归档了几个文件、总大小);
    • 回答:为什么"先确认打包成功再删原文件"这一步不能省?

综合任务

  1. 设计一套"服务器上排查问题的标准流程":
    • 场景:用户反馈"下单接口很慢",你登录服务器,只有命令行;
    • 要求:
      • 列出排查步骤(从"确认现象"到"定位原因"),每步给出具体命令;
      • 说明每一步在看什么、什么结果说明什么问题;
      • 说明"不该做什么"(提示:不要一上来就重启、不要直接改生产配置);
      • 说明如何留下证据(便于事后复盘);
    • 额外要求:把这套流程对照 《执行计划与慢查询定位》 的"慢查询定位"与 《日志框架与日志规范》 的"日志/指标/链路追踪",说明它们在信息层次上的分工。
分级提示

提示 1(方向):第 1 题的方向是"它默认什么都搜"。

提示 2(关键点):第 5 题——cat 一个 2GB 文件会怎样?能不能省掉 cat?

提示 3(接近答案):第 7 题的"信息层次"——Linux 命令看"这台机器现在怎样",慢查询日志看"数据库怎样",链路追踪看"一次请求经过了哪些服务"。

12. 参考答案 ​

基础题
  1. 错。grep -r 会搜索所有文件(包括 dist/、target/、node_modules/)✗

实测证据(7(二)):不排除时前 5 个结果全是构建产物 ✓

为什么它是"默认行为":grep 不知道哪些是构建产物 ——那是你的项目知识 ✓

所以要显式排除:

bash
grep -r "xxx" . --exclude-dir={dist,target,node_modules,.git}

更省事的做法:配一个别名或一个脚本 ✓

bash
# ~/.bashrc
alias g='grep -rIn --exclude-dir={dist,target,node_modules,.git,.idea}'
# 之后:g "ERROR" .

-I 也很有用:跳过二进制文件 ✓(否则 grep 会输出 "Binary file xxx matches" 这种无用的行)

  1. 覆盖写入;追加写入;2>&1 ✓

记忆:一个 > 是"冲掉",两个 >> 是"接着写" ✓

2>&1 的含义拆解:

text
2   = 标准错误(stderr)的文件描述符
>   = 重定向
&1  = "文件描述符 1 现在指向的地方"(标准输出)
合起来 = 让 stderr 去 stdout 现在去的地方 ✓

⚠️ 那个 & 不能少:2>1 是"把错误写进一个叫 1 的文件" ✗(完全不同)

  1. 三个理由:
理由说明
① 可组合性 ★★N 个小工具能组合出远多于 N 种用途 ✓ ——这是"指数级"的能力增长
② 每个都好学好记sort 就是排序,wc 就是计数 —— 不需要学一个庞大的软件 ✓
③ 可复用学会 sort -n,在所有场景都能用 ✓

一个对比:

text
"大而全"的工具:  提供了 100 个功能,但第 101 件事做不了 ✗
小工具组合:      30 个命令,能组合出几乎无限种用途 ✓

还有一个工程上的好处:

组合出来的命令是"可分享的" ——一句话就能写进运维手册、发到群里 ✓

"你跑一下 awk '{print $9}' access.log | sort | uniq -c | sort -rn | head"比"你打开 XX 软件,点第三个标签,再点导出,再……"要高效得多 ✓✓

代码阅读题(第 4、5 题)

第 4 题:逐步拆解

bash
cat app.log                          # ① 输出整个文件
  | grep "ERROR"                     # ② 只保留含 ERROR 的行
  | awk '{print $2}'                 # ③ 取每行的第 2 列(假设是错误类型/接口名)
  | sort                             # ④ 排序(让相同的挨在一起)
  | uniq -c                          # ⑤ 合并相邻相同行,并计数 ★
  | sort -rn                         # ⑥ 按计数**倒序**排序(r=reverse, n=按数值)
  | head -5                          # ⑦ 取前 5

结果:"出现最多的 5 种错误(按第 2 列归类)及其次数" ✓

★ 关键在第 ④ 步:uniq 只能合并"相邻的"重复行 ——所以必须先 sort ✓

(这是新手最容易错的一点:以为 uniq 自己会去重,其实它只处理相邻行 ✗)

第 5 题:2GB 文件的问题

问题说明
cat 是多余的 ✗**`cat file
内存sort 需要把数据放进内存(或临时文件)——2GB 的匹配结果可能撑爆内存 ⚠️
耗时全文件扫描 2GB 本身就要时间

改进版:

bash
grep "ERROR" app.log | awk '{print $2}' | sort | uniq -c | sort -rn | head -5

进一步的优化:

优化做法
先缩小范围如果只要今天的:`grep "$(date '+%F')" app.log
用 awk 一次做完筛选+取列 ★`awk '/ERROR/ {print $2}' app.log
甚至用 awk 做计数 ★★`awk '/ERROR/ {count[$2]++} END {for (k in count) print count[k], k}' app.log

最后那条的价值:

text
原来的管道:cat → grep → awk → sort → uniq → sort → head   (7 个进程)
优化后:    awk → sort → head                              (3 个进程)

"能用一个工具做完的,就别用一串管道" ——但前提是它仍然可读 ✓

可读性与性能的权衡:日常排查用管道(清晰),超大文件或高频任务用 awk(快) ✓

⚠️ 一个安全提醒:处理 GB 级日志时注意磁盘 ——grep ... > errors.txt 也可能写出一个巨大的文件 ⚠️ (先 grep -c 看看有多少条,再决定要不要落盘 ✓)

编程题(第 6 题)
bash
#!/usr/bin/env bash
set -uo pipefail

LOG_DIR="${1:-/var/log/myapp}"
ARCHIVE_DIR="${2:-/var/log/myapp/archive}"
DAYS="${3:-7}"

mkdir -p "$ARCHIVE_DIR"
today=$(date '+%F')
archive="$ARCHIVE_DIR/logs-$today.tar.gz"

echo "归档目录 : $LOG_DIR"
echo "保留天数 : $DAYS 天以内的日志不归档"

# ① 找出旧日志(-mtime +7 = 7 天前修改的;-type f 只处理文件)
mapfile -t old_logs < <(find "$LOG_DIR" -maxdepth 1 -type f -name '*.log' -mtime "+$DAYS")

if [ "${#old_logs[@]}" -eq 0 ]; then
    echo "没有需要归档的日志 ✓"
    exit 0
fi

echo "待归档文件数 : ${#old_logs[@]}"

# ② 打包(用 -C 切目录,避免把绝对路径打进去)
#    注意:用 printf '%s\0' + tar --null 处理"文件名含空格"的情况
printf '%s\0' "${old_logs[@]}" | tar -czf "$archive" --null -T -

# ③ ★ 关键:检查打包是否成功,成功才删除
if [ $? -ne 0 ] || [ ! -s "$archive" ]; then
    echo "✗ 打包失败或归档文件为空,**不删除原文件**"
    exit 1
fi

echo "归档文件 : $archive"
echo "归档大小 : $(du -h "$archive" | cut -f1)"

# ④ 确认后再删
rm -f "${old_logs[@]}"

echo "已删除原文件 : ${#old_logs[@]} 个 ✓"
echo "归档完成 ✓"

关键点说明:

#做法为什么
①find -mtime +7 而不是 +7 天"以内"-mtime +7 = 严格早于 7 天前 ✓(-7 是 7 天内)⚠️
②用 -C 或相对路径打包避免解压时覆盖原路径 ✗
③★ 检查 $? 与 -s(文件非空)打包失败时删除 = 数据永久丢失 ✗✗✗
④先 echo 再 rm可追溯(留下了"删了哪些"的记录)✓
⑤printf ... \0 + tar --null -T -正确处理文件名含空格的情况 ✓(新手常漏)

为什么"先确认打包成功再删原文件"不能省

因为删除是不可逆的,而打包是可能失败的 ✓

打包失败的原因(都不是罕见情况):

原因说明
磁盘满了 ✗归档文件写了一半 → tar 失败,但退出码可能"看起来成功"(所以要检查 -s 非空)✓
权限不足某些日志文件读不了 ✗
文件被占用/正在写入日志还在被写 → 打包出的内容不完整 ⚠️
路径错误变量拼错 → 打包了空内容 ✗

一个真实的事故模式:

bash
tar -czf archive.tar.gz logs/ && rm -rf logs/
# 看起来很严谨(用了 &&),但——
# 如果磁盘满了,tar 可能创建了 archive.tar.gz 却只写了一部分
# && 判断"tar 退出码为 0"就继续删了 ✗
# 结果:归档是坏的,原文件没了 ✗✗✗

所以要做"双重确认":

bash
tar -czf "$archive" ... || exit 1          # ① 退出码
[ -s "$archive" ] || exit 1                # ② 文件非空
tar -tzf "$archive" >/dev/null || exit 1   # ③ ★ 归档能被正常读回来(最强的验证)

第 ③ 条最稳:"能解压/能列出内容"才算真的成功 ✓

这与 《Git 分支、合并与回退》 讲的"reset --hard 前先备份"是同一种谨慎:

对不可逆操作,都要先有一个"可验证的成功"作为前提 ✓

而且验证要"真的读回来",而不是"看退出码" ——退出码可能在"部分失败"时也是 0 ⚠️

补充:生产环境的日志归档还应该考虑:

考虑说明
日志轮转(logrotate)通常交给工具做,比自己写脚本更可靠 ✓
归档保留多久合规要求(如 6 个月)✓
归档也要有清理规则否则归档目录自己会撑爆磁盘 ✗
综合任务(第 7 题)

排查流程(含命令)

步做什么命令看什么 / 说明什么
①确认现象curl -w "%{time_total}\n" -o /dev/null -s http://localhost:8080/api/orders到底多慢?("很慢"没有信息量)✓
②看服务在不在ps -ef | grep java / systemctl status myapp进程是否还活着 ✓
③看实时日志 ★tail -f logs/app.log | grep -i error有没有异常在刷 ✓
④看错误数量趋势grep -c ERROR logs/app.log是不是突然变多 ✓
⑤看机器负载 ★top / uptimeCPU、内存、load ——load 高说明"排队" ✓
⑥看磁盘df -h磁盘满了会导致各种诡异问题 ✓
⑦看连接/端口ss -tlnp | grep 8080端口在不在监听 ✓
⑧看 GC(Java)jstat -gcutil <pid> 1000 10频繁 Full GC → 内存问题 ✓
⑨看数据库见 《执行计划与慢查询定位》 的慢查询慢在哪条 SQL ✓
⑩看依赖服务curl 下游 / 看超时日志是不是被别人拖慢 ✓

每一步"在看什么":

层命令回答的问题
应用tail -f"我的代码在报错吗?"
进程top / jstat"这台机器的资源够吗?"
系统df -h / uptime"机器本身健康吗?"
依赖curl / 日志"是不是被下游拖慢?"
数据慢查询"是不是 SQL 慢?"

"不该做什么":

不该做为什么
一上来就重启 ✗✗重启会清掉现场(内存、连接、线程栈)——你再也没法知道刚才发生了什么 ✓
直接改生产配置 ✗没有经过验证的改动 = 第二次事故 ✗
在生产上跑"测试性"的重命令如 rm、DROP、全表更新 ✗✗
只改现象不改原因(如调大超时)只是把问题藏起来 ⚠️
不记录就操作事后无法复盘 ✗

★ 第一条最重要:"先保留现场,再恢复服务"

text
重启前至少做:
  · jstack <pid> > /tmp/thread-$(date +%s).txt      # 线程栈
  · jmap -histo:live <pid> > /tmp/histo.txt         # 内存直方图(或 heap dump)
  · 复制一份最近的日志

为什么:重启能恢复服务,但只有现场能告诉你"为什么会这样" ✓ ——否则同样的问题一定会再来一次 ✓

如何留下证据:

证据怎么留
命令的输出command > /tmp/排查-$(date +%s).txt 2>&1 ✓
完整操作记录 ★script 命令(记录整个终端会话)或用带日志的终端工具 ✓
时间线记录"几点发现、几点做了什么、效果如何" ✓
快照线程栈、堆、GC 日志、慢查询 ✓

对照 《执行计划与慢查询定位》 与 《日志框架与日志规范》:信息层次

层次工具回答粒度
机器/进程(本节)top、df、ps、jstat"这台机器现在怎样"一台机器 ✓
数据库(《执行计划与慢查询定位》)慢查询、EXPLAIN"哪条 SQL 慢、为什么"一条 SQL ✓
日志(《日志框架与日志规范》)应用日志 + traceId"某一次请求发生了什么"一次请求 ✓
指标(《日志框架与日志规范》)Prometheus/Grafana"整体趋势与当前水位"聚合 ✓
链路追踪(《日志框架与日志规范》)Jaeger/SkyWalking"一次请求慢在哪一段"跨服务 ✓

它们的协作方式(一个完整的排查):

text
① 指标(Grafana)发现:"订单接口 P99 从 200ms 涨到 3s"        ← 发现异常
② 机器层(top):CPU 正常、内存正常、磁盘正常                  ← 排除机器问题
③ 日志(按 traceId):看到一次请求里 DB 段耗时 2.8s            ← 缩小到数据库
④ 慢查询日志(S3-015):找到那条 SQL,EXPLAIN 发现没走索引     ← 定位原因
⑤ 链路追踪:确认"就是这个 DB 段"(并看看有没有别的服务也慢)    ← 交叉验证

一句话总结:

Linux 命令是"底层体检"(这台机器怎么了),慢查询是"专科检查"(数据库怎么了),日志/指标/链路是"病历与监护仪"(这次请求怎么了、长期趋势如何)。 ✓

排查问题时,通常是"监护仪发现异常 → 体检排除环境 → 病历定位到具体请求 → 专科确认根因" ✓

这也呼应了 《执行计划与慢查询定位》 开头那句:"先测量,再优化" ✓

13. 掌握标准 ​

  • [ ] 能说出 /、~、/etc、/var/log、/opt 各自放什么;
  • [ ] 会用 ls / cd / pwd / mkdir / cp / mv / rm(知道 rm -rf 的风险);
  • [ ] 会用 tail -f 看实时日志 ★;
  • [ ] 会用 grep 查找内容,并记得排除构建产物 ★;
  • [ ] 理解管道,能把 2~3 个命令组合起来 ★★★;
  • [ ] 能区分 > 与 >>,知道 2>&1 的作用与顺序;
  • [ ] 知道 $? 是退出码及其意义;
  • [ ] 知道 sort | uniq -c 的用法(以及"必须先 sort");
  • [ ] 会 tar 的打包/查看/解压;
  • [ ] 理解"小工具 + 组合"的 Unix 哲学。

14. 扩展资源 ​

15. 下一步学习 ​

《Linux 文件、用户与权限管理》:理解 ls -l 里那串 -rw-r--r-- 到底是什么意思。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引