第四周 · 综合实战

实战场景

凌晨 3 点,你被告警吵醒:线上 Web 服务器 CPU 100%,磁盘 I/O 爆满,SSH 登录极慢。你需要远程登录排查故障、定位肇事进程、清理磁盘、设置监控防止复发。


环境搭建

先模拟故障环境(不需要真正搞垮机器,只是搭建练习环境):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cd /tmp
mkdir -p incident-response/{logs,scripts,archive}

# 模拟 Web 日志(正常 + 异常混合)
cat > incident-response/logs/access.log <<'EOF'
192.168.1.10 - - [10/Jan/2024:03:00:01] "GET /api/health HTTP/1.1" 200 12
192.168.1.10 - - [10/Jan/2024:03:00:02] "GET /api/users HTTP/1.1" 500 45
192.168.1.10 - - [10/Jan/2024:03:00:03] "GET /api/orders HTTP/1.1" 200 234
192.168.1.20 - - [10/Jan/2024:03:00:04] "POST /api/upload HTTP/1.1" 500 67
192.168.1.10 - - [10/Jan/2024:03:00:05] "GET /api/health HTTP/1.1" 200 12
192.168.1.20 - - [10/Jan/2024:03:00:06] "GET /api/products HTTP/1.1" 404 50
192.168.1.20 - - [10/Jan/2024:03:00:07] "POST /api/upload HTTP/1.1" 500 67
192.168.1.10 - - [10/Jan/2024:03:00:08] "GET /api/health HTTP/1.1" 200 12
192.168.1.30 - - [10/Jan/2024:03:00:09] "GET /api/users HTTP/1.1" 200 89
192.168.1.30 - - [10/Jan/2024:03:00:10] "POST /api/upload HTTP/1.1" 500 67
192.168.1.10 - - [10/Jan/2024:03:00:11] "GET /api/health HTTP/1.1" 200 12
192.168.1.20 - - [10/Jan/2024:03:00:12] "GET /api/search?q=test HTTP/1.1" 200 567
192.168.1.10 - - [10/Jan/2024:03:00:13] "POST /api/upload HTTP/1.1" 500 67
192.168.1.30 - - [10/Jan/2024:03:00:14] "GET /api/products HTTP/1.1" 200 345
192.168.1.10 - - [10/Jan/2024:03:00:15] "GET /api/health HTTP/1.1" 200 12
EOF

# 模拟一些巨大的"垃圾"文件
dd if=/dev/zero of=incident-response/logs/debug.log bs=1M count=100 2>/dev/null
dd if=/dev/zero of=incident-response/archive/old-backup-2023.tar.gz bs=1M count=200 2>/dev/null
dd if=/dev/zero of=incident-response/scripts/dump.tmp bs=1M count=50 2>/dev/null

# 模拟配置文件
cat > incident-response/scripts/config.ini <<'EOF'
# Database config
host=localhost
port=3306
user=app_user
# password=old_secret_123 ← 这条需要注释掉
# Debug settings
debug=false
log_level=warn
EOF

任务一:登录与初始侦查

你 SSH 到服务器,先快速摸清系统状态。

  1. uptime——负载多少?服务器运行了多久?
  2. free -h——内存够吗?
  3. df -h——磁盘有没有满?哪个分区最紧张?
  4. who——有没有其他人在线?(凌晨 3 点不太应该)
  5. uname -a——确认系统版本

任务二:找到磁盘杀手

df -h 显示磁盘空间紧张,你需要找出大文件来释放。

  1. du -sh /tmp/incident-response/* 看看各子目录占用
  2. du -sk /tmp/incident-response/*/* | sort -rn | head -10 找出最大的 10 个文件
  3. 确认 old-backup-2023.tar.gz 是 3 年前的备份,可以删除
  4. 删除最占空间的无用文件:old-backup-2023.tar.gzdebug.logdump.tmp
  5. 再次 df -h 确认空间释放(注意:如果用的是 /tmp 下的文件,/tmp 可能不在独立分区——这时 df 不变,但 du 显示空间已释放)

任务三:日志分析找问题

分析 access.log,找出异常的访问模式。

  1. 统计 access.log 的总请求数
  2. 统计每种 HTTP 状态码(200/404/500)各出现多少次
  3. 找出 500 错误的请求,统计是哪个接口最多
  4. 哪个 IP 的请求量最大?(按访问次数排序)
  5. 某个 IP 疯狂请求 /api/upload 并返回 500——提取该 IP 的所有请求记录,看看时间分布

任务四:配置安全加固

config.ini 里有明文密码需要处理。

  1. grep 找出 config.ini 中包含 password 的行
  2. 用 sed 把 password=old_secret_123 替换为 password=***REDACTED***(直接在文件上修改)
  3. 验证修改后的文件密码行已脱敏
  4. config.ini 权限设置为 600(只有所有者可读写)

任务五:监控布防

防止问题复发,设置一个定时巡检。

编写检查脚本 check-disk.sh

1
2
3
4
5
6
#!/bin/bash
# 检查 / 分区使用率,超过 80% 告警
USAGE=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$USAGE" -gt 80 ]; then
echo "[$(date)] WARNING: disk usage is ${USAGE}%" >> /tmp/disk-alert.log
fi
  1. 创建此脚本,设置 chmod 755
  2. 手动测试脚本
  3. 设置 crontab:每 5 分钟执行一次
  4. 等待 5-10 分钟,检查 /tmp/disk-alert.log

故障排查一

现象:你 scp check-disk.sh user@server:/opt/scripts/ 失败,报:

1
scp: /opt/scripts/check-disk.sh: Permission denied
  • 原因是什么?(至少 3 个可能)
  • 如何逐项排查?按什么顺序?

故障排查二

现象:你 curl -I http://localhost:8080 返回 curl: (7) Failed to connect to localhost port 8080: Connection refused

  • 这个报错意味着什么?
  • 排查步骤:列出你要依次执行的命令和原因
  • 如果你怀疑服务挂了但不知道服务名叫什么,怎么找?(提示:进程列表、日志、配置文件)

故障排查三(加分题)

现象:cron 定时执行的 check-disk.sh 产生了告警日志,但 df -h 显示磁盘正常。

  • df 阈值设的是 80%,但告警说 USAGE=91%——两个数字对不上,可能的原因?
  • 排查思路:脚本里 df -h / | tail -1 | awk '{print $5}'——这个命令链每个环节都可能引入什么问题?

Day 1:网络诊断

今日目标:检测网络连通性、查看 HTTP 响应、下载文件、查看端口监听——网络出问题能第一时间定位 🎯

命令速览

命令 用途 核心参数
ping 测试网络连通性 -c N (发 N 个包后停止)
curl 发送 HTTP 请求 / 下载文件 -I (只看头) / -O (下载)
wget 下载文件 直接跟 URL
ss 查看 socket 状态(现代替代 netstat) -tulnp
netstat 查看网络连接(传统命令) -tulnp
ip addr 查看网络接口信息(现代替代 ifconfig) show
ifconfig 查看网络接口(传统命令) (无参数)

逐个击破

① ping — 测试网络连通性

  • 语法ping [OPTIONS] <TARGET>
  • 参数-c N→发 N 个包后停止(不指定则会一直 ping)
  • 上手练 🖥️
    1. ping -c 4 8.8.8.8——ping Google DNS,验证 Internet 连通
    2. ping -c 3 localhost——ping 本机
    3. ping -c 4 www.baidu.com——ping 域名(同时验证 DNS 解析)
    4. 观察输出:关注 time=(延迟)和 packet loss(丢包率)
  • ⚠️ 常见坑:有些服务器禁用 ICMP(ping 不通不代表连不上),用 curl 验证 HTTP 连通

② curl — HTTP 请求与下载

  • 语法curl [OPTIONS] <URL>
  • 参数
    • -I→只获取 HTTP 响应头
    • -O→下载文件(保持原名)
    • -o 文件名→下载并指定文件名
    • -v→显示详细请求过程(排查用)
    • -L→跟随重定向
  • 上手练 🖥️
    1. curl -I https://www.baidu.com——只看响应头(状态码、服务器类型等)
    2. curl https://httpbin.org/get——发送 GET 请求,看返回的 JSON
    3. curl -O https://www.example.com/index.html——下载文件(example.com 可能无内容,换一个真实 URL)
    4. curl -v https://www.baidu.com 2>&1 | head -20——看详细的 TCP+TLS 握手过程
    5. 模拟 POST:curl -X POST -d "name=test" https://httpbin.org/post
  • ⚠️ 常见坑:curl 默认不跟随重定向,加 -L 才能自动跳转;-I 用的是 HEAD 请求,部分服务器不支持

③ wget — 文件下载

  • 语法wget <URL>
  • 上手练 🖥️
    1. wget https://www.example.com/index.html——下载文件
    2. wget -O myfile.html https://www.example.com/——下载并指定文件名
    3. wget -c <URL>——断点续传
  • ⚠️ 常见坑wget 不是所有系统默认安装(minimal 镜像可能没有),curl 更通用

④ ss — 查看端口监听

  • 语法ss -tulnp
  • 参数
    • -t→TCP,-u→UDP,-l→正在监听的,-n→不解析服务名(显示端口号),-p→显示进程
  • 上手练 🖥️
    1. ss -tulnp——查看所有正在监听的端口
    2. ss -tulnp | grep 22——查找 SSH 端口
    3. ss -tan——查看所有 TCP 连接(含已建立的)
    4. ss -s——汇总统计
  • ⚠️ 常见坑ss 是 netstat 的现代替代,部分老系统只有 netstat(参数相同:netstat -tulnp

⑤ ip addr — 查看网络接口

  • 语法ip addr show 或简写 ip a
  • 上手练 🖥️
    1. ip addr show——查看所有网卡信息
    2. ip a | grep "inet "——只提取 IPv4 地址
    3. ip route show——查看路由表
  • ⚠️ 常见坑ip 是现代命令,老系统用 ifconfiglo(loopback)的 IP 总是 127.0.0.1

今日必刷(全部终端实操)💪

  1. ping 百度 4 个包,记录平均延迟和丢包率
  2. curl -I 查看百度的服务器类型(Server 头)
  3. ss -tulnp 找出你机器上所有在监听的 TCP 端口(可能需要 sudo)
  4. ip addrifconfig 找到你机器的内网 IP 地址
  5. 排错题:你 ping www.some-random-domain-12345.comName or service not known——排查步骤:先 ping IP 如 8.8.8.8,再检查 DNS 解析(nslookupdig),问题可能出在哪?
  6. 排错题curl http://localhost:8080Connection refused——端口没监听。如何确认这个端口上是否有服务?如果确认没有,可能是什么原因?

📚 命令详解

命令 详细参考
ping 网络管理-ping
curl 网络管理-curl
wget 网络管理-wget
ss 网络管理-ss
netstat 网络管理-netstat
ip 网络管理-ip
ifconfig 网络管理-ifconfig

Day 2:SSH 远程连接与文件传输

今日目标:用 SSH 免密登录远程服务器,用 scp 安全传输文件——不用每次输密码 🎯

命令速览

命令 用途 核心参数
ssh 远程登录 -p (端口)
ssh-keygen 生成 SSH 密钥对 -t rsa
ssh-copy-id 复制公钥到远程服务器 (指定用户@主机)
scp 安全复制文件 -r (递归目录) / -P (端口)

逐个击破

① ssh — 远程登录

  • 语法ssh [user@]host [-p port]
  • 上手练 🖥️
    1. ssh localhost——SSH 到自己(如果没装 ssh server 可能失败)
    2. ssh -p 2222 user@remote-host——指定端口连接
    3. ssh user@remote-host "ls -l /var/log"——不登录,直接执行远程命令
    4. ssh -v user@remote-host——调试模式(排查连接问题)
  • ⚠️ 常见坑:第一次连接新主机会提示确认指纹(host key verification),输入 yes 后连接,这是防止中间人攻击的机制

② ssh-keygen — 生成密钥对

  • 语法ssh-keygen -t rsa
  • 上手练 🖥️
    1. ssh-keygen -t rsa——生成密钥对(一路回车使用默认设置)
    2. ls -l ~/.ssh/——查看生成的 id_rsa(私钥)和 id_rsa.pub(公钥)
    3. cat ~/.ssh/id_rsa.pub——查看公钥内容
    4. ssh-keygen -t rsa -b 4096 -f ~/.ssh/my_custom_key——生成 4096 位指定名称的密钥
  • ⚠️ 致命坑私钥(id_rsa)绝对不能泄露!权限必须是 600chmod 600 ~/.ssh/id_rsa),否则 SSH 会因权限太宽松而拒绝使用

③ ssh-copy-id — 复制公钥实现免密登录

  • 语法ssh-copy-id user@host
  • 上手练 🖥️
    1. ssh-copy-id localhost——把自己的公钥复制到本机(需要本机有 ssh server)
    2. 测试免密:ssh localhost——不再需要输密码
    3. 如果 ssh-copy-id 不可用,手动方式:
      1
      cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
  • ⚠️ 常见坑:远程服务器 ~/.ssh/ 权限必须是 700authorized_keys 必须是 600,否则免密登录失败

④ scp — 安全复制

  • 语法scp [OPTIONS] <SRC> <TARGET>
  • 参数-r→递归复制目录,-P→指定端口(注意大写!)
  • 上手练 🖥️
    1. echo "test scp" > /tmp/scp-test.txt
    2. scp /tmp/scp-test.txt user@remote-host:/tmp/——本地上传到远程(需要真实远程主机,或 localhost
    3. scp user@remote-host:/etc/hosts /tmp/hosts.remote——从远程下载
    4. scp -r /tmp/myproject/ user@remote-host:/tmp/——复制整个目录
    5. scp -P 2222 file.txt user@host:/tmp/——指定端口(注意是大写 -P
  • ⚠️ 常见坑:scp 的端口参数是 -P(大写),ssh 的是 -p(小写),记混了会报错

今日必刷(全部终端实操)💪

  1. 生成你自己的 SSH 密钥对(如果已有则查看),确认私钥权限为 600
  2. 如果本机有 ssh server:用 ssh-copy-id localhost 实现本机免密登录
  3. ssh localhost "hostname && whoami && pwd" 执行一条远程命令链
  4. 创建 /tmp/scp-demo.txt,用 scp 复制到 localhost:/tmp/scp-demo-2.txt(免密或输密码)
  5. 排错题ssh user@remote-hostPermission denied (publickey)——排查:公钥复制了吗?私钥权限对吗?远程 ~/.ssh/authorized_keys 权限对吗?写出排查步骤
  6. 排错题scp -p 2222 file.txt user@host:/tmp/ 没按预期连接 2222 端口(scp 的 -p 是小写,表示保留文件属性而非端口;ssh 用 -p 指定端口,scp 用 -P 指定端口)——怎么修正?为什么 ssh 和 scp 的端口参数不一样?

📚 命令详解

命令 详细参考
ssh 网络管理-ssh
ssh-keygen 网络管理-ssh
ssh-copy-id 网络管理-ssh
scp 网络管理-scp

Day 3:系统资源监控

今日目标:会看磁盘空间、目录大小、内存使用、系统负载、内核版本——机器状态一目了然 🎯

命令速览

命令 用途 核心参数
df 查看磁盘分区可用空间 -h (人类可读)
du 查看文件/目录占用空间 -sh (汇总) / -sk (KB)
free 查看内存使用情况 -h (人类可读)
uptime 查看系统运行时间与负载 (无参数)
uname 查看内核版本与系统架构 -a (全部信息)
who 查看当前登录用户 (无参数)

逐个击破

① df — 磁盘空间

  • 语法df -h
  • 上手练 🖥️
    1. df -h——查看所有挂载分区的使用情况
    2. df -h /——只看根分区
    3. df -h /tmp——如果 /tmp 是独立分区会单独显示
    4. df -ih——查看 inode 使用情况(小文件过多 inode 也会耗尽)
  • ⚠️ 常见坑df 显示磁盘满了但 du 显示还有空间——可能是已删除但进程还在写的文件占用了空间(lsof | grep deleted

② du — 目录占用空间

  • 语法du -sh <DIR> / du -sh * / du -sk * | sort -n
  • 上手练 🖥️
    1. du -sh /var/log——查看日志目录占用总空间
    2. du -sh /var/log/*——查看每个子项的大小
    3. du -sk /var/log/* | sort -rn | head -10——找到最大的 10 个日志文件(KB 为单位)
    4. du -sh --max-depth=1 ~/——只看 home 目录下一层的占用
    5. du -sh * .[^.]*——包含隐藏文件(.bashrc 等)
  • ⚠️ 常见坑du 统计的是实际占用的磁盘块(block),与 ls -l 看到的文件大小可能不同(稀疏文件)

③ free — 内存使用

  • 语法free -h
  • 上手练 🖥️
    1. free -h——查看总内存、已用、空闲、缓存
    2. free -h -s 2——每 2 秒刷新一次(Ctrl+C 退出)
    3. 理解输出:available 才是真正可用的(含可释放的缓存),不要只看 free
  • ⚠️ 常见坑:Linux 会利用空闲内存做磁盘缓存(buff/cache),看到 free 很小别慌——看 available 列才是真实可用内存

④ uptime — 系统负载

  • 语法uptime
  • 上手练 🖥️
    1. uptime——查看运行时间、用户数、负载
    2. 理解负载:三个数字分别是 1 分钟 / 5 分钟 / 15 分钟的平均负载
    3. 负载 ≈ CPU 核心数时系统满负荷,超过核心数则排队等待
    4. 查看 CPU 核心数:nproccat /proc/cpuinfo | grep processor | wc -l
  • ⚠️ 常见坑:负载高不代表 CPU 忙——等待 I/O(磁盘读写)的进程也会计入负载

⑤ uname — 系统信息

  • 语法uname -a
  • 上手练 🖥️
    1. uname——只显示内核名称
    2. uname -r——内核版本号
    3. uname -a——全部信息(内核名、主机名、版本、架构等)
    4. uname -m——硬件架构(x86_64 表示 64 位)
  • ⚠️ 常见坑uname -r 显示的内核版本和发行版版本(lsb_release -a)是两回事

⑥ who — 当前登录用户

  • 语法who
  • 上手练 🖥️
    1. who——查看哪些用户登录了
    2. who -b——查看系统最后启动时间
    3. w——比 who 更详细,还显示每个用户在运行什么命令
    4. last——查看历史登录记录
  • ⚠️ 常见坑who 只显示通过终端的登录,不显示通过 Web 或 API 访问的用户

今日必刷(全部终端实操)💪

  1. df -h 查看你机器上还有多少磁盘空间,找出占用最高的分区
  2. du -sh ~/ 查看你的 home 目录总大小
  3. 找出你 home 目录下最大的 5 个文件或目录(提示:du -sk ~/* | sort -rn | head -5
  4. 查看系统内存和负载(free -h + uptime),对比负载和 CPU 核心数
  5. 排错题df -h 显示 / 分区使用了 95%,但你 rm -rf 删了一个 2G 的日志文件后,df 显示仍然是 95%——为什么?怎么释放空间?(提示:lsof
  6. 排错题free -h 显示 available 只有 200M,你认为内存不够了,但同事说”Linux 内存管理就这样,可用的在 cache 里”——你怎么验证同事说的是对是错?

📚 命令详解

命令 详细参考
df 文件与目录管理-df
du 文件与目录管理-du
free 系统管理-free
uptime 进程管理-uptime
uname 系统管理-uname
who 权限与用户管理-who

Day 2:grep 正则与上下文

今日目标:用扩展正则精确匹配复杂模式,用上下文参数看匹配行前后内容 🎯

命令速览

参数 用途
-E 扩展正则(支持 + ? {} () `
-A N 显示匹配行及后面 N 行(After)
-B N 显示匹配行及前面 N 行(Before)
-C N 显示匹配行及前后各 N 行(Context)

正则速查

符号 含义 示例
^ 行首 ^Error 以 Error 开头
$ 行尾 ;$ 以分号结尾
. 任意单个字符 h.t 匹配 hat/hot/hit
* 前一个字符出现 0 次或多次 ab*c 匹配 ac/abc/abbc
[] 字符集 [Ee]rror 匹配 Error 或 error
[^] 排除字符集 [^a-z] 非小写字母
+ 前一个字符出现 1 次或多次(需 -E) ab+c → abc/abbc
? 前一个字符出现 0 次或 1 次(需 -E) colou?r → color/colour
{n,m} 前一个字符出现 n~m 次(需 -E) [0-9]{2,4} 2-4 位数字
() 分组(需 -E) `(error
` ` 或(需 -E)

逐个击破

① 扩展正则 — grep -E

先准备测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > /tmp/grep-re.txt <<EOF
Error: connection refused
ERROR: timeout after 30s
WARN: retry attempt 1
error: disk full
INFO: all systems operational
DEBUG: processing request 12345
user@host connected from 192.168.1.100
2024-01-15 08:30:22 app[1234]: started
colour
color
abc
abbc
abbbc
EOF

🖥️ 上手练

  1. grep -E "^(Error|ERROR)" /tmp/grep-re.txt——匹配 Error 或 ERROR 开头的行
  2. grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /tmp/grep-re.txt——提取 IP 地址
  3. grep "colou*r" /tmp/grep-re.txt——基础正则可匹配 colour(u* = u 出现 0 次或多次)
  4. grep -E "colou?r" /tmp/grep-re.txt——扩展正则匹配 color/colour(u? = u 出现 0/1 次)
  5. grep -E "ab+c" /tmp/grep-re.txt——匹配 abc/abbc/abbbc(b 至少 1 次)
  6. grep -E "ab{2}c" /tmp/grep-re.txt——只匹配 abbc(b 恰好 2 次)
  7. grep -E "(error|warn)" /tmp/grep-re.txt -i——匹配 error 或 warn(-i 忽略大小写)

② 上下文参数 — -A / -B / -C

🖥️ 上手练

  1. grep -A3 "ERROR" /tmp/grep-re.txt——显示 ERROR 行及其后面 3 行
  2. grep -B2 "started" /tmp/grep-re.txt——显示 started 行及其前面 2 行
  3. grep -C2 "timeout" /tmp/grep-re.txt——显示 timeout 行及其前后各 2 行
  4. 实战:查看某条日志前后发生了什么:
    1
    grep -C5 "error" /var/log/syslog 2>/dev/null | head -30

③ 正则组合实战

  1. 匹配所有以 192.168 开头的 IP:grep -E "192\.168\.[0-9]{1,3}\.[0-9]{1,3}"
  2. 匹配邮箱地址:grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
  3. 匹配时间戳格式 HH:MM:SSgrep -E "[0-9]{2}:[0-9]{2}:[0-9]{2}"
  4. 排除空行和注释行:grep -vE "^(#|$)" file

💪 今日必刷(全部终端实操)

  1. grep -E/tmp/grep-re.txt 中提取所有 IP 地址(只输出匹配的 IP,不输出整行)
  2. 统计 /tmp/grep-re.txt 中以 Ee 开头的行数(忽略大小写)
  3. 查看 /etc/ssh/sshd_config 中包含 Port 的行及其前后各 1 行
  4. 排除 /etc/ssh/sshd_config 中的注释行和空行,只显示有效配置行数
  5. 排错题grep "error|warn" /tmp/grep-re.txt 什么也匹配不到——为什么?修正这条命令
  6. 排错题:你想查看 nginx 日志中某个 IP 10.0.0.5 的访问记录前后 5 行上下文,于是写了 grep -C5 "10.0.0.5" /var/log/nginx/access.log,结果也匹配到了 10.0.0.5010.0.0.51——如何精确匹配只匹配 10.0.0.5 这个 IP?(提示:IP 在行中的位置特点)

📚 命令详解

命令 详细参考
grep 文件查看与文本处理-grep

Day 3:sed 流编辑

今日目标:用 sed 做替换、删除行、提取行——不改文件时预览,确认无误后直接修改 🎯

命令速览

操作 语法 用途
替换 sed 's/old/new/g' 全局替换
就地修改 sed -i 's/old/new/g' 直接修改文件(危险但常用)
删除行 sed '3,5d' 删除第 3~5 行
打印行 sed -n '10p' 只打印第 10 行
行范围操作 sed '/start/,/end/d' 删除从 start 到 end 的行块

逐个击破

① 替换 — s/old/new/g

  • 语法sed 's/查找/替换/标志' <FILE>
  • 标志g→全局替换(每行所有匹配),不加 g 只替换每行第一个匹配
  • 🖥️ 上手练(先准备数据):
    1
    2
    3
    4
    5
    6
    cat > /tmp/sed-test.txt <<EOF
    hello world
    hello linux
    hello world hello again
    goodbye world
    EOF
    1. sed 's/hello/hi/' /tmp/sed-test.txt——只替换每行第一个 hello
    2. sed 's/hello/hi/g' /tmp/sed-test.txt——全局替换所有 hello
    3. sed 's/world/earth/' /tmp/sed-test.txt——替换 world
    4. sed 's/hello/hi/g; s/world/earth/g' /tmp/sed-test.txt——同时做多个替换(分号分隔)
  • ⚠️ 常见坑:sed 默认输出到终端,不会修改文件,加 -i 才直接修改

② 就地修改 — sed -i

  • 语法sed -i 's/old/new/g' <FILE>
  • 🖥️ 上手练
    1. cp /tmp/sed-test.txt /tmp/sed-test.bak——先备份
    2. sed -i 's/hello/hi/g' /tmp/sed-test.txt && cat /tmp/sed-test.txt——直接修改了
    3. 恢复:cp /tmp/sed-test.bak /tmp/sed-test.txt
    4. sed -i.bak 's/hello/hi/' /tmp/sed-test.txt——-i.bak 生成 .bak 备份文件
  • ⚠️ 致命坑sed -i 不可逆!没有 git 时先备份

③ 按行删除 — d

  • 语法sed '<N或RANGE>d' <FILE>
  • 🖥️ 上手练(创建测试数据):
    1
    seq 1 10 > /tmp/sed-num.txt
    1. sed '3d' /tmp/sed-num.txt——删除第 3 行
    2. sed '3,5d' /tmp/sed-num.txt——删除第 3~5 行
    3. sed '1d' /tmp/sed-num.txt——删除第 1 行
    4. sed '/^5/,/^8/d' /tmp/sed-num.txt——删除从 “5” 开头到 “8” 开头的行块
    5. sed '/^#/d' /etc/ssh/sshd_config——删除所有注释行

④ 打印指定行 — p

  • 语法sed -n '<N>p' <FILE>-n 抑制默认输出,配合 p 只打印指定行)
  • 🖥️ 上手练
    1. sed -n '1p' /tmp/sed-num.txt——打印第 1 行
    2. sed -n '5,8p' /tmp/sed-num.txt——打印第 5~8 行
    3. sed -n '$p' /tmp/sed-num.txt——打印最后一行($ 在 sed 中表示最后一行)

⑤ 行范围操作

  • 🖥️ 上手练
    1. sed -n '/^2/,/^6/p' /tmp/sed-num.txt——打印从 “2” 开头到 “6” 开头的行范围
    2. sed '/^3/,/^7/s/.*/REPLACED/' /tmp/sed-num.txt——在 3~7 行范围内做替换
    3. 实战:删除 C 风格注释块 /* ... */(略)

💪 今日必刷(全部终端实操)

  1. /etc/ssh/sshd_config 中所有 yes 替换为 no(先不要用 -i,只输出看效果)
  2. 用 sed 删除 /etc/ssh/sshd_config 的所有注释行和空行,输出有效配置(管道也行)
  3. 打印 /etc/passwd 的第 1 行和第 10 行(用 sed 实现,不用 head+tail)
  4. 用 sed 批量替换——将 /tmp/sed-test.txthello 改为 hiworld 改为 earth(一条命令)
  5. 实际修改:用 sed -i/tmp/sed-num.txt 中的奇数行注释掉(每行前加 #
  6. 排错题:执行 sed -i 's/a/b/' fileinvalid command code——可能原因是什么?如何排查?
  7. 排错题:执行 sed 's/\//-/g' /tmp/path.txt 发现把所有 / 都替换了,但你只想替换行首的 /——如何限制替换范围?

📚 命令详解

命令 详细参考
sed 文件查看与文本处理-sed

Day 4:awk 列处理

今日目标:用 awk 按列处理数据——提取列、条件过滤、数值计算、统计汇总 🎯

命令速览

功能 语法 用途
提取列 awk '{print $1, $NF}' 打印第 1 列和最后一列
指定分隔符 awk -F: 以冒号分隔(默认空格/Tab)
条件过滤 awk '$3 > 10' 第 3 列大于 10 才输出
求和 awk '{sum+=$1} END{print sum}' 累加第 1 列
行数 awk 'END{print NR}' 总行数

逐个击破

① 列提取 — $1 / $NF / $0

  • 语法awk '{print $N}'$1=第一列,$2=第二列,$NF=最后一列,$0=整行
  • 🖥️ 上手练
    1. echo "alice 25 engineer" | awk '{print $1}'——打印第 1 列 alice
    2. echo "alice 25 engineer" | awk '{print $NF}'——打印最后一列 engineer
    3. echo "alice 25 engineer" | awk '{print $1, $3}'——打印第 1、3 列
    4. awk '{print $1}' /etc/passwd——提取所有用户名(默认 : 不是分隔符!所以打印整行)
    5. awk -F: '{print $1}' /etc/passwd——用 : 分隔,正确提取用户名

② 指定分隔符 — -F

  • 语法awk -F'<DELIM>'
  • 🖥️ 上手练
    1. awk -F: '{print $1, $7}' /etc/passwd——用户名和 shell
    2. awk -F: '{print $1, $3}' /etc/passwd——用户名和 UID
    3. awk -F, '{print $2}' /tmp/data.csv——CSV 文件的第二列
    4. awk -F'[ :]' '{print $1, $NF}' /tmp/mixed.txt——多个分隔符(空格或冒号)
    5. echo "192.168.1.1 - - [10/Jan/2024:08:30:00]" | awk -F'[][]' '{print $2}'——提取方括号内的时间戳

③ 条件过滤

  • 语法awk '条件 {动作}'
  • 🖥️ 上手练
    1. awk -F: '$3 > 1000' /etc/passwd——UID 大于 1000 的用户(通常是普通用户)
    2. awk -F: '$3 == 0' /etc/passwd——UID 为 0 的用户(root)
    3. awk -F: '$7 == "/bin/bash"' /etc/passwd——使用 bash 的用户
    4. awk -F: '$1 ~ /^a/' /etc/passwd——用户名以 a 开头的(~ 是正则匹配)
    5. awk -F: 'length($1) < 5' /etc/passwd——用户名长度小于 5 的

④ 数值计算

  • 语法awk '{sum+=$1} END{print sum}'
  • 🖥️ 上手练(创建测试数据):
    1
    2
    3
    4
    5
    6
    7
    cat > /tmp/awk-scores.txt <<EOF
    alice 85
    bob 92
    charlie 78
    diana 95
    eve 88
    EOF
    1. awk '{sum+=$2} END{print sum}' /tmp/awk-scores.txt——总分
    2. awk '{sum+=$2} END{print sum/NR}' /tmp/awk-scores.txt——平均分
    3. awk '$2 > 85' /tmp/awk-scores.txt——成绩大于 85 的人
    4. awk '{if($2>90) print $1,"excellent"; else if($2>80) print $1,"good"; else print $1,"pass"}' /tmp/awk-scores.txt——评级
    5. awk 'END{print NR}' /tmp/awk-scores.txt——总行数(等价于 wc -l

⑤ BEGIN/END 块

  • 语法awk 'BEGIN{初始化} {每行动作} END{汇总输出}'
  • 🖥️ 上手练
    1. awk -F: 'BEGIN{print "用户名\tUID"} {print $1"\t"$3} END{print "总共 " NR " 个用户"}' /etc/passwd——带表头和汇总
    2. awk 'BEGIN{max=0} {if($2>max) max=$2} END{print "最高分:", max}' /tmp/awk-scores.txt——找最大值

💪 今日必刷(全部终端实操)

  1. /etc/passwd 中提取所有用户名和对应的 shell,用 : 隔开输出(如 root:/bin/bash
  2. 统计 /etc/passwd 中 UID 大于等于 1000 的用户数量(提示:用累加计数器变量,END 输出计数值;注意 END 块的 NR 永远是总行数而非过滤后行数)
  3. 计算 /tmp/awk-scores.txt 中所有人的平均分、最高分、最低分(一条 awk 命令)
  4. 模拟 nginx 日志分析:统计每个 HTTP 状态码的出现次数(假设日志第 9 列是状态码)
  5. 排错题awk -F: '{print $2}' /etc/passwd 输出了密码占位符 x——你想要提取真正的密码哈希,但得到的只是 x 而不是哈希值。为什么?(提示:shadow 和 passwd 的区别)
  6. 排错题awk '{print $1}' /etc/passwd 打印了整行——默认分隔符是空格,但 passwd 用冒号分隔。如何正确提取用户名?

📚 命令详解

命令 详细参考
awk 文件查看与文本处理-awk

Day 5:打包与压缩

今日目标:用 tar 打包归档,用 gzip/bzip2 压缩,用 unzip 解压 zip——打包 ≠ 压缩,搞清楚区别 🎯

命令速览

命令 用途 关键参数
tar -cvf 创建 tar 归档(不压缩,只打包) c创建 v详细 f文件
tar -xvf 解包 tar 归档 x解包
tar -czvf 创建 gzip 压缩的 tar.gz z=gzip
tar -xzvf 解包 tar.gz z=gzip
tar -cjvf 创建 bzip2 压缩的 tar.bz2 j=bzip2
gzip / gunzip 压缩/解压单个文件(不是归档)
unzip 解压 .zip 文件

逐个击破

① tar 归档(不压缩)

  • 语法tar -cvf <ARCHIVE.TAR> <SRC>
  • 🖥️ 上手练(先准备数据):
    1
    2
    3
    4
    mkdir -p /tmp/tar-demo/{docs,src}
    echo "readme" > /tmp/tar-demo/README
    echo "hello" > /tmp/tar-demo/src/main.sh
    echo "config" > /tmp/tar-demo/docs/config.txt
    1. tar -cvf /tmp/demo.tar -C /tmp tar-demo/——打包(-C 先切换到 /tmp)
    2. ls -lh /tmp/demo.tar——查看包大小(接近原文件总和,没压缩)
    3. rm -rf /tmp/tar-demo——删除原始目录
    4. tar -xvf /tmp/demo.tar -C /tmp——解包到 /tmp
    5. ls -lR /tmp/tar-demo/——验证文件完整还原
  • ⚠️ 常见坑:tar 打包路径是相对路径——如果打包时用了绝对路径,解包时也会按绝对路径覆盖(危险!)

② tar.gz 压缩归档

  • 语法tar -czvf <ARCHIVE.TAR.GZ> <SRC> / tar -xzvf <ARCHIVE.TAR.GZ>
  • 🖥️ 上手练
    1. tar -czvf /tmp/demo.tar.gz -C /tmp tar-demo/——创建 gzip 压缩包
    2. ls -lh /tmp/demo.tar /tmp/demo.tar.gz——对比大小,压缩后明显更小
    3. rm -rf /tmp/tar-demo——删除
    4. tar -xzvf /tmp/demo.tar.gz -C /tmp——解压
    5. 验证文件一样:diff -r /tmp/tar-demo /tmp/tar-demo-orig(如果有备份的话)

③ tar.bz2 压缩归档

  • 语法tar -cjvf <ARCHIVE.TAR.BZ2> <SRC> / tar -xjvf <ARCHIVE.TAR.BZ2>
  • 🖥️ 上手练
    1. tar -cjvf /tmp/demo.tar.bz2 -C /tmp tar-demo/
    2. ls -lh /tmp/demo.tar.gz /tmp/demo.tar.bz2——bzip2 通常比 gzip 压缩率更高但更慢

④ gzip / gunzip — 单文件压缩

  • 语法gzip <FILE> / gunzip <FILE.GZ>
  • 🖥️ 上手练
    1. cp /etc/hosts /tmp/hosts.copy && ls -lh /tmp/hosts.copy——记录原始大小
    2. gzip /tmp/hosts.copy && ls -lh /tmp/hosts.copy.gz——压缩后 .copy 变成了 .copy.gz
    3. gunzip /tmp/hosts.copy.gz && ls -lh /tmp/hosts.copy——解压恢复
  • ⚠️ 常见坑gzip 只能压缩单个文件,不能压缩目录。目录要用 tar + gzip 组合

⑤ unzip — 解压 .zip 文件

  • 语法unzip <FILE.ZIP>
  • 🖥️ 上手练
    1. cd /tmp && zip -r demo.zip tar-demo/(如果没有 zip 命令:apt install zip 或跳过)
    2. unzip -l /tmp/demo.zip——列出 zip 包内容(不解压)
    3. unzip /tmp/demo.zip -d /tmp/unzipped/——解压到指定目录
  • ⚠️ 常见坑.tar.gz.zip 是不同的格式,.tar.gz 用 tar 解,.zip 用 unzip 解

💪 今日必刷(全部终端实操)

  1. 创建 /tmp/myproject/ 目录,里面放 3 个子目录各 2 个文件,打包为 myproject.tar
  2. 把同一个目录打包压缩为 myproject.tar.gzmyproject.tar.bz2,对比三个文件的大小
  3. 删除原目录,用 tar -xzvfmyproject.tar.gz 恢复,验证文件完整性
  4. tar -tzvf /tmp/myproject.tar.gz 查看压缩包内容(不解压直接列出)
  5. 排错题:你从网上下载了 app.tar.gz,执行 unzip app.tar.gzEnd-of-central-directory signature not found——为什么?正确的解压命令是什么?
  6. 排错题:打包时用 tar -czvf /tmp/backup.tar.gz /etc/nginx/(绝对路径),解压 tar -xzvf /tmp/backup.tar.gz 后文件被解压到了 /etc/nginx/ 而不是当前目录——为什么?如何安全打包绝对路径?

📚 命令详解

命令 详细参考
tar 压缩与归档-tar
gzip 压缩与归档-gzip
gunzip 压缩与归档-gzip
unzip zip-unzip

第三周 · 综合实战

实战场景

你接手了一个遗留项目的日志目录,需要分析日志、清洗数据、生成报告并打包归档。所有操作必须用 grep/sed/awk 完成。


环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cd /tmp
mkdir -p log-cleanup/{raw,reports}

cat > log-cleanup/raw/app.log <<'EOF'
2024-01-01 10:00:01 ERROR database connection failed
2024-01-01 10:00:05 INFO retrying connection
2024-01-01 10:00:10 ERROR database connection failed
2024-01-01 10:01:00 WARN slow query detected: 5.2s
2024-01-01 10:02:00 INFO connection established
2024-01-01 10:05:00 ERROR permission denied for user 'app'
2024-01-01 10:05:05 ERROR permission denied for user 'app'
2024-01-01 10:05:10 ERROR permission denied for user 'app'
2024-01-01 10:10:00 WARN memory usage 85%
2024-01-01 10:15:00 ERROR database connection failed
2024-01-01 10:20:00 INFO backup started
2024-01-01 10:25:00 ERROR timeout on request #4521
2024-01-01 10:30:00 ERROR timeout on request #4523
2024-01-01 10:30:05 WARN disk usage 78%
2024-01-01 10:35:00 INFO backup completed successfully
EOF

# 再创建第二个日志文件(模拟多文件场景)
cat > log-cleanup/raw/error.log <<'EOF'
2024-01-01 09:00:00 CRITICAL out of memory
2024-01-01 09:01:00 ERROR database connection failed
2024-01-01 09:02:00 WARN swap usage high
2024-01-01 09:03:00 CRITICAL kernel panic detected
2024-01-01 09:05:00 ERROR timeout on request #1001
EOF

任务一:日志过滤与统计(grep)

  1. 统计 app.log 中 ERROR 级别日志的总条数
  2. 统计 app.log 中每种日志级别(ERROR/WARN/INFO)各多少条,按数量从多到少排列
  3. 找出包含 database connection failed 的所有行,显示行号和上下文(前后各 1 行)
  4. 从所有日志文件中递归搜索 CRITICAL,忽略大小写,显示文件名和行号

任务二:数据清洗与转换(sed)

  1. 用 sed 把 app.log 中所有的 ERROR 改为 [ERROR](加方括号),只输出看效果,不要改文件
  2. 删除 error.log 中包含 WARN 的行(输出到终端)
  3. 用 sed 提取 app.log 的第 3~8 行
  4. 批量替换:创建一个 CSV 文件 /tmp/log-cleanup/data.csv,用 sed 把逗号替换为制表符(\t)

任务三:数据分析(awk)

  1. 统计 app.log 中每种错误类型的出现次数(错误类型 = 最后一个字段),从多到少排列
  2. 提取 app.log 中 ERROR 行的时间戳(第 2 列,格式 HH:MM:SS),只输出时间部分
  3. 计算 app.log 的日志时间跨度——最早和最晚的时间戳差多少(思路:提取时间戳,排序,取首尾)
  4. 用 awk 创建一个汇总报告:显示总行数、ERROR 数、WARN 数、INFO 数、CRITICAL 数

任务四:打包归档

  1. log-cleanup/raw/ 下的所有 .log 文件打包压缩为 logs-$(date +%Y%m%d).tar.gz
  2. 查看压缩包内容(不解压),确认文件列表
  3. 创建 archive/ 目录,将压缩包移动到 archive/
  4. 清理 raw/ 目录下的原始日志(确认压缩包正常后再删)

故障排查一

现象:你执行 grep "database connection failed" /tmp/log-cleanup/raw/app.log | awk '{print $1, $2}' 想提取时间戳,但只打印了第一行的时间戳就停了。不是管道的问题。

  • 检查 grep 的输出是否完整(grep ... | wc -l
  • awk 默认处理所有行,为什么只出了一行?(提示:缓冲?不,没那么复杂——检查原始数据是否真的有多个匹配行)
  • 实际排查:你的 grep 正常输出多行,但 awk 只打印了一行——可能是哪里出了问题?

故障排查二

现象:用 tar -czvf /tmp/log-cleanup/logs.tar.gz /tmp/log-cleanup/raw/ 打包后,解压发现目录结构是 tmp/log-cleanup/raw/app.log 而不是你期望的 raw/app.log

  • 为什么会多出 tmp/log-cleanup/
  • 如何打包时避免绝对路径问题?(写出正确的命令)
  • 如果包已经打了,解压时如何控制输出路径避免覆盖现有文件?

Day 1:grep 基础

今日目标:用 grep 在文件中快速过滤内容——大小写、反向匹配、递归搜索、显示行号、只输出匹配部分 🎯

命令速览

参数 用途
grep "pattern" 基本搜索
-i 忽略大小写
-v 反向匹配(排除匹配行)
-r 递归搜索目录
-n 显示行号
-o 只输出匹配的部分(不输出整行)

逐个击破

① grep 基本搜索

  • 语法grep [options] "pattern" <FILE>
  • 🖥️ 上手练
    1. grep root /etc/passwd——找出包含 root 的行
    2. grep /bin/bash /etc/passwd——找出使用 bash 的用户(注意 / 不是特殊字符)
    3. grep "server" /etc/hosts /etc/hostname——同时在多个文件中搜索

② -i 忽略大小写

  • 🖥️ 上手练
    1. echo -e "Error\nERROR\nerror\nInfo" > /tmp/grep-test.txt
    2. grep error /tmp/grep-test.txt——只匹配小写
    3. grep -i error /tmp/grep-test.txt——全匹配,3 行都出来
  • ⚠️ 常见坑:Linux 下大小写敏感,日志里 ErrorERROR 是不同的,加 -i 统一匹配

③ -v 反向匹配

  • 🖥️ 上手练
    1. grep -v root /etc/passwd——排除 root,看其他用户
    2. grep -v "^#" /etc/ssh/sshd_config——排除注释行(^# 表示以 # 开头)
    3. grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"——同时排除空行(^$ 是空行)
  • ⚠️ 常见坑-v 排除后还要排除空行,否则输出里全是没有实际内容的空行

④ -r 递归搜索

  • 🖥️ 上手练
    1. grep -r "127.0.0.1" /etc/——在 /etc 整个目录下搜索(可能需要 sudo 处理部分无权限文件)
    2. grep -r "localhost" /etc/ 2>/dev/null——忽略权限错误
    3. grep -rl "localhost" /etc/ 2>/dev/null——只显示包含匹配的文件名,不显示内容
  • ⚠️ 常见坑:递归搜索大目录(如 /)会很慢,先限制目录范围;2>/dev/null 屏蔽权限错误

⑤ -n 显示行号

  • 🖥️ 上手练
    1. grep -n root /etc/passwd——每行前面带行号
    2. grep -rn "127.0.0.1" /etc/ 2>/dev/null——递归 + 行号,方便定位

⑥ -o 只输出匹配部分

  • 🖥️ 上手练
    1. echo "IP=192.168.1.1 PORT=8080" | grep -o "[0-9.]*"——只提取 IP 地址
    2. grep -o "root" /etc/passwd——每行 root 出现几次就输出几次
    3. grep -o "[a-zA-Z]*" /tmp/grep-test.txt | sort | uniq -c | sort -rn——统计词频

💪 今日必刷(全部终端实操)

  1. /etc/passwd 中查找所有使用 /sbin/nologin 的用户行,显示行号
  2. 统计 /etc/ssh/sshd_config 中有效配置行(排除注释和空行)的数量
  3. 递归搜索 /etc/ 下所有包含 root 的文件(只显示文件名),忽略权限错误
  4. ps aux 的输出中提取所有以 /usr 开头的进程路径(用 grep -o
  5. 排错题grep error /var/log/syslog 只匹配到小写 error,但日志里还有 ERRORError——修正命令
  6. 排错题grep -r "password" /etc/ 报了大量 Permission denied,想忽略这些权限错误怎么办?

📚 命令详解

命令 详细参考
grep 文件查看与文本处理-grep
0%