第一弹把常用命令过完之后,很多人会进入一种奇怪的状态:单个命令都眼熟,ls、cat、grep、ps 都能说出是干什么的,可真给你一台卡住的服务器,还是会愣在原地。这很正常,命令只是单词,排查问题是语法。所以第二弹我就不重复基础指令了,而是把最常见的小故障切成五组练习,外加一个关于作业控制的彩蛋,帮大家把“下一步该敲什么”这个决策链练顺。
这套练习适合刚学完命令行基础、想往“能干活”方向走的 Linux 初学者,也适合从开发转运维的朋友拿来当手感训练。每次练习大概 30 到 50 分钟,不需要复杂环境,一台虚拟机和普通用户账号就够。我建议你别急着看完就划走,跟着敲一遍,错误信息才是最值钱的部分。
1. 先交代背景:这组练习解决什么问题
1.1 第一弹之后的常见瓶颈
第一弹里大家通常会把文件操作、目录跳转、打包解压、进程查看这些命令都过一遍。但真到用的时候,我发现几个非常普遍的卡点:
一是不知道命令该组合着用。比如查内存占用只知道free,查哪个进程吃掉内存却不知道ps aux后面该接管道还是该排序;二是权限出问题只想到chmod 777,完全绕开属主、属组、ACL 这些概念;三是遇到服务连不上就到处乱试,ping一下、curl一下,没有清晰的排查顺序。
这些问题不是“知道命令”能解决的,得靠“做小实验”把思路练成本能。第二弹的五组练习,每一组都对应一种常见故障场景:权限、进程、文本处理、网络、自动化。每组都是先给一个有逼真的小环境,再让你自己动手查、动手改。
1.2 练习环境和前置命令清单
我这次用了一台 2 核 4G 的虚拟机和某个常见的开源发行版,桌面版和纯命令行版本都无所谓,下面涉及的命令在绝大多数 Linux 环境里都能直接跑。你需要准备两个东西:
- 一个普通用户账号,最好已经会
sudo提权; - 一个可以随时丢掉重来的实验环境。后面凡是涉及
kill、chmod、userdel的操作,都别在还跑着业务的机器上照抄。
命令层面,默认你已经会用cd、ls、cat、grep、vi,不熟悉的可以先把这些命令翻出来看一眼。练习里出现的新指令我会带着解释,但不准备重新讲基础。
1.3 两个必须记住的安全前提
做这套练习前,我把话放在前面:第一,不要用 root 账号直接测权限练习,那样什么都看不出来;第二,练习用完之后,把临时用户和临时目录删干净。模拟故障的时候,系统偶尔会“看起来很严重”,比如负载飙高、进程变僵尸,这都是预期内的现象,记得练习本身是可控的就行。
下面开始。
2. 练习一:用户与权限,把“最小权限”刻进肌肉记忆
2.1 用 15 分钟搭一组“权限陷阱屋”
很多人以为权限就是chmod 777和chmod 755的区别,这其实把问题想窄了。权限永远要结合“谁访问、谁修改、放在什么场景”来判断。这个练习我习惯让所有人搭一个模拟的 Web 应用目录:
sudo groupadd webdev sudo useradd -m -G webdev alice sudo useradd -m -G webdev bob mkdir -p ~/lab2/public ~/lab2/secret ~/lab2/scratch chmod 755 ~/lab2/public chmod 700 ~/lab2/secret chmod 1777 ~/lab2/scratch ls -l ~/lab2搭完之后,先用alice账号往三个目录里各放一个测试文件,再切到bob账号去读、去写,把下面的表格填一遍。会立刻发现问题:scratch是 1777,所有人都能写;secret是 700,只有目录主人能进;public是 755,别人能读但改不了。
| 目录 | 权限 | 其他用户能否读 | 其他用户能否写 | 实际场景举例 |
|---|---|---|---|---|
| ~/lab2/public | 755 | 能 | 否 | 静态资源、对外发布 |
| ~/lab2/secret | 700 | 否 | 否 | 私钥、配置备份 |
| ~/lab2/scratch | 1777 | 能 | 能 | 临时交换目录 |
真正让你记住权限的,是切换用户实验的瞬间:你不是“看懂了”权限位,而是亲眼看到Permission denied弹出来。这就是为什么我强调别用 root 来练,root 能无视一切权限位,练完等于白练。
2.2 为什么目录执行位比读位还关键
这个练习里最容易出现的小误区,是搞不懂目录为什么一定有执行位。很多人把chmod 644给一个目录,结果其他人连ls都进不去。原因很简单:文件上的执行位代表“是否能运行”,目录上的执行位代表“是否能穿过这一层目录访问里面的内容”。
想理解这个,可以试两组对照命令:
chmod 644 ~/lab2/testdir chmod 755 ~/lab2/testdir ls -l ~/lab2/testdir当目录是 644 时,别人就算有读权限,也无法把目录当路径“走进去”。所以在给目录设置权限时,执行位通常不能省。这也是为什么常规 Web 目录用 755、私密目录用 700——这里 755 不是网上传的“好看”,而是保证目录可以被进程穿过、内容可以读,但写操作必须单独授权。
2.3 ACL 与 umask:隐藏“默认权限”的几个坑
基础权限位练完后,再加一个小任务:给~/lab2/public单独允许bob写入,但其他人仍然只能读。一般人的第一反应是改属组,但如果我们不想把整个组都放开,就可以用 ACL:
sudo setfacl -m u:bob:rwx ~/lab2/public getfacl ~/lab2/publicgetfacl会显示bob:rwx这一行,说明用户级 ACL 生效了。注意,ACL 与普通权限位是叠加关系,如果你之后再执行chmod 777,ACL 里那条规则会变得非常难排查。这个练习我建议同时看一眼umask:
umask touch ~/lab2/public/newfile ls -l ~/lab2/public/newfileumask 022意味着新文件默认是 644,新目录默认是 755。换一下umask 077再建一次文件,看看新文件的权限变成了什么。思考题是:为什么系统默认不让新建文件带执行位?答案是防止你无意识生成可执行文件,这是安全设计,不是缺陷。
3. 练习二:进程与资源排查,一次“假慢”定位
3.1 三个命令快速锁定嫌疑进程
第二个场景是管理员最常遇到的抱怨:服务器变慢了。不熟悉排查的人会一上来就盯着free看,看到内存没用完就转头怀疑带宽。实际上第一步应该在“进程列表”里找异常消耗者。
我把练习设计成:先手动制造一个 CPU 跑满的情况,再来排查。开两个终端,第一个终端执行:
nice -n 10 dd if=/dev/zero of=/dev/null &然后在第二个终端依次执行:
top ps aux --sort=-%cpu | head ps aux --sort=-%mem | headps的好处是结果稳定,适合肉眼阅读;top的好处是实时刷新,能看动态变化。我会让练习者重点观察%CPU、%MEM和TIME+这三列:TIME+特别能说明问题,一个进程虽然此刻占用不高,但累计 CPU 时间已经很大,说明它可能是长期运行的反弹脚本或死循环任务。
3.2 kill 与信号:为什么 15 和 9 不能乱用
锁定进程后,大多数人都会直接kill -9。但我一直强调,能救的进程尽量别用kill -9,因为 -9 信号让内核直接把进程拔掉,进程自己完全没机会做清理工作;而默认的kill发的是 15 号 SIGTERM,相当于通知进程“请你结束自己”,数据库、配置文件写入这类工作才能在退出前把状态落盘。
练习里我会让你分别试三种收尾方式:
kill 进程号 # 相当于 kill -15,先尝试优雅停止 kill -INT 进程号 # 模拟按 Ctrl+C kill -9 进程号 # 内核强制终止一个非常实用的观察点:kill -15之后去ps看进程可能还在,因为程序正在处理退出逻辑。别急着又补一发kill -9,给个几秒钟。有些守护进程在持久化状态时确实会慢。真正确认行为可疑、或者进程已经变成僵尸,再用 -9 也不迟。
3.3 用可控方式制造一次 CPU 飙升并收尾
练习里我习惯让学生运行dd if=/dev/zero of=/dev/null或者yes > /dev/null,把 CPU 打到单核 100%。然后观察load average后面的三个数字。这里有个常见误读:load average 不是“CPU 占用率”,它统计的是处于可运行状态的进程数量,和 CPU 数量有关系。如果机器是 2 核,load 到 4 已经很异常;如果是 8 核,load 为 4 说明还有余量。
收尾时把后台进程全部清掉:
kill %1 # 结束第一个后台作业 wait这个练习我只允许你制造“忙等”,不要去生产机器上跑fork炸弹之类的演示。负载场景和数据丢失风险完全不匹配,没必要玩。
4. 练习三:文本处理三剑客,面对真实日志不再发怵
4.1 准备一份“业务日志样本”
第三个练习经常是运维面试里的小题,也是实际排查中很出效果的一项。我先给出一小段模拟网关访问日志,字段分别是 IP、时间、请求方式、URL、状态码、响应时长和请求大小:
mkdir -p ~/lab2/logs cat > ~/lab2/logs/access.log <<'EOF' 192.0.2.10 [20/Jan/2025:10:01:23 +0800] GET /index.html 200 0.023 2048 192.0.2.11 [20/Jan/2025:10:02:45 +0800] GET /api/list 502 0.101 512 198.51.100.7 [20/Jan/2025:10:04:12 +0800] POST /api/login 404 0.003 96 192.0.2.10 [20/Jan/2025:10:05:37 +0800] GET /page/about 200 0.014 4096 198.51.100.7 [20/Jan/2025:10:07:55 +0800] POST /api/login 500 0.402 64 203.0.113.5 [20/Jan/2025:10:09:06 +0800] GET /assets/app.js 304 0.002 0 192.0.2.11 [20/Jan/2025:10:11:44 +0800] GET /api/list 200 0.019 1024 EOF这份文件是我按常见格式编的,内容完全虚构,但字段结构足够做一轮完整练习。如果手头已有真实系统日志,替换进去效果更好。
grep、sed、awk三个命令常被叫“三剑客”,但各自擅长的事不一样:grep负责按条件找行,sed负责按规则改流,awk负责把文本拆成字段做统计。很多人背了一堆参数却不会用,就是因为没分清这个分工。
4.2 grep 筛选、sed 清洗、awk 统计的分工
第一个需求:找出所有 5xx 响应,并统计出现次数。
grep ' 5[0-9][0-9] ' ~/lab2/logs/access.log grep -c ' 5[0-9][0-9] ' ~/lab2/logs/access.log这里的坑点是为了避免把500错误码和普通数字混在一起而加的。一个更复杂的场景:把日志里的时间戳去掉,只看 IP、方法和状态码。sed用圆括号保留需要的部分:
sed -E 's#^([^ ]+) .*\. .* ([0-9]{3}) .*#\1 \2#' ~/lab2/logs/access.log如果要统计每个 IP 的请求次数,那就轮到awk和排序命令配合:
awk '{print $1}' ~/lab2/logs/access.log | sort | uniq -c | sort -rn做的时候请留意两点:第一,awk默认按空格拆分字段,如果日志里时间中间带有空格,$4往往不完整,解决方案是改字段分隔符或改用方括号截断;第二,sort | uniq -c | sort -rn这串管道要背下来,它是后续统计的通用流水线。
4.3 串起三剑客的极简报表脚本
把这套逻辑串起来就是一个小报表:统计每个请求路径的平均响应时间,只看耗时超过 0.1 秒的行。
awk '{if ($6 > 0.1) print $4, $6}' ~/lab2/logs/access.log | sed 's#/# #g' | awk '{sum[$1]+=$NF; count[$1]++} END {for (k in sum) print k, sum[k]/count[k]}'命令虽然只有一行,但思路很清晰:先用awk过滤慢请求,再用sed把 URL 里的斜杠替换成空格便于取路径段,最后按路径段分组求平均。实际生产里我会写成脚本而不是一行长命令,但练习阶段用一行拼出来,反而能让管道思维更清晰。
5. 练习四:网络排查,把“连不上”拆成三个问题
5.1 一张排查顺序表:DNS、路由、端口、防火墙
第四个练习我让大家处理一个经典问题:A 机器访问 B 机器的 Web 服务超时。很多人的第一反应是按“ping 不通 = 对方挂了”来判断,但这个判断太粗了。我会先给一段判断顺序表:
| 层级 | 问题 | 常用命令 | 典型结论 |
|---|---|---|---|
| DNS 解析 | 域名能不能解析 | dig、nslookup、getent hosts | NXDOMAIN 或超时 |
| 路由可达 | 网络通不通 | ping、traceroute | 丢包、卡在某一跳 |
| 端口监听 | 服务起没起 | ss -tlnp | 没有监听端口 |
| 防火墙过滤 | 端口通不通 | nc -vz、curl -v | connection refused 或 timeout |
先查 DNS 的原因是要排除“名字解析慢造成虚假超时”,这类问题往往 ping 域名能通,但访问服务名时系统去解析另一个环境,反而等了半天。DNS 没问题再看网络层,最后看服务本身。
5.2 curl -v 和 ss -tlnp 的黄金搭档
本地做模拟时,可以起一个最简单的 HTTP 服务:
python3 -m http.server 8000 & ss -tlnp | grep 8000 curl -v http://127.0.0.1:8000/curl -v这个参数千万不要省,它会完整打印连接过程,包括解析到哪个地址、建立的 TCP 连接、TLS 握手在哪一步断掉。很多人纠结“服务连不上”,其实curl -v已经把答案打在脸上了:如果卡在connect to ... timed out,多半是防火墙或路由丢包;如果是connection refused,那是服务没监听或端口不对。
调试端口连通性时再补充一个命令:
nc -vz 127.0.0.1 8000如果看到succeeded,说明端口已经被监听;如果直接卡住不返回,那基本可以断定是中间设备在丢包。这里特别提醒一句:不要把nc -vz放在不信任的网络环境里乱扫别人主机,练习只对自己本机和实验环境用。
5.3 验证链路:本地回环、内网、跨网段
我会让练习者分别用三条链路验证同一个“服务不可达”问题:
curl -v http://127.0.0.1:8000/ # 本地回环失败:服务本身 curl -v http://192.0.2.10:8000/ # 本机内网 IP:网卡绑定/防火墙 curl -v http://203.0.113.5:8000/ # 另一台机器:路由/防火墙/对方服务本地回环都不通,说明服务没起来或绑定了错误地址;本地回环通、内网 IP 不通,基本是防火墙和监听地址问题;内网能通、外网超时,才轮到路由和跨网段策略。这套从近到远的排查法,比挂在嘴上的“重启服务器”要靠谱得多。完成之后记得把临时服务停掉,不然下次练习会碰到端口冲突。
6. 练习五:定时任务与日志回滚,让系统“自洁”
6.1 先写一个能自证的 crontab
自动化是很多初学者觉得无从下手的领域。最简单也最实用的入口,是先写一个固定时间点执行的任务。前面几组练习都会产生临时文件,那就让系统每天自动清理一次练习目录里的tmp垃圾:
mkdir -p ~/lab2/cleanup cat > ~/lab2/cleanup/clean-old.sh <<'EOF' #!/bin/bash find ~/lab2/logs -name "*.log" -mtime +7 -delete echo "cleaned at $(date)" >> ~/lab2/cleanup/clean.log EOF chmod +x ~/lab2/cleanup/clean-old.sh crontab -e在 crontab 里写入这一行:
0 3 * * * /home/你的用户名/lab2/cleanup/clean-old.sh这个时间我故意选凌晨三点,只是示例,实际可以根据业务低谷调整。写完用crontab -l确认生效,再提前验证一下脚本本身能不能跑,而不是干等第二天看结果。脚本路径、执行权限有没有给,是最常见的失败原因。
6.2 logrotate 配置与常见失效原因
定时任务负责执行动作,日志滚动负责控制日志体积。我给一个简单配置示例,放在/etc/logrotate.d/lab2里面:
/lab2/logs/*.log { daily rotate 7 missingok notifempty compress delaycompress }意思是:每天做一次滚动,保留 7 份历史日志,日志为空也不报错,不现场压缩最新一份,而是压缩再前一次的副本。加了delaycompress之后,应用进程还可以往旧日志文件里临时写一小段时间,能减少“日志文件还在被占用”的报错概率。
无效原因通常有:第一,应用进程一直握着文件句柄不释放,即使滚动完成,新日志还是写进旧文件,可以用copytruncate参数处理;第二,日志文件目录没有写权限;第三,logrotate是按照文件名模式匹配的,如果你写的是/lab2/logs/*.log,子目录里滚动就不会覆盖。这个练习验证方式很简单:手动执行sudo logrotate -vf /etc/logrotate.d/lab2,观察输出里的rotating pattern和considering log两行,就能确认配置到底有没有被系统接受。
7. 彩蛋:误按 Ctrl+Z 后,进程到底去哪了
7.1 前后台作业的三个命令演示
这个彩蛋来自实际事故:有人用vi编辑配置时误按了 Ctrl+Z,发现界面退到 shell,以为编辑器已经保存退出,直接去重启服务。结果服务把缺失的配置读了一遍,当场起不来。原因就是 Ctrl+Z 不是关闭进程,而是把进程挂起,丢到了后台。
完整走一遍就懂了:
sleep 300 # 按下 Ctrl+Z jobs bg fgjobs会显示[1]+ Stopped sleep 300,bg把挂起的作业转为后台继续跑,fg再次把它调回前台。这里最要命的是:如果你直接关掉终端,这个后台作业会被 SIGHUP 信号带走,进程随之消失。所以真正想“裸奔”的进程,得用下面两招之一。
7.2 nohup 与 disown 的合理使用场景
演示完正常流程,再模拟断开终端的场景。一个办法是让进程在启动时忽略挂断信号:
nohup ./long-task.sh > /tmp/long-task.log 2>&1 &另一个办法是作业已经运行才发现需要脱离终端,那就用disown:
sleep 600 & disown这样即使 SSH 断开,作业也不会被终端退出信号波及。但我仍然要补一句:生产环境里要求长期跑的进程,正确方案是注册成服务或交给调度器去管,而不是依赖nohup裸奔。nohup适合临时观察脚本和一次性任务,真正高频的服务一旦会话误杀,日志文件都没人收,很容易变成僵尸数据危机。
8. 我做完这组练习后的几条实操体会
五组练习全部跑过一遍之后,我最明显的感觉不是“命令记住了”,而是遇到报错不再慌。以前看到Permission denied第一反应是找 sudo,现在我会先看属主、属组、权限位,再看是不是 ACL 在捣乱;以前看到服务连不上,我会直接重启服务,现在我会先按 DNS、路由、端口、防火墙的顺序走一遍,往往是防火墙的问题,重启服务纯属白费。
这组练习之所以强调“小”,是因为每件事都能在几分钟内得到反馈。权限改错了,马上Permission denied;进程收不掉,马上ps能看到状态;awk写错,立刻输出一串 NaN 或者空行。这种即时反馈,比读十篇文档都管用。
最后再分享一个小习惯:练习的时候记得开history时间戳记录,加一行export HISTTIMEFORMAT="%F %T ",回头翻自己敲过的命令就很清楚当时哪一步卡住了。这个习惯坚持下来,过两周回看,你会发现以前的“疑难杂症”不过就是几只纸老虎。