1. 学习笔记 day02:从能敲命令到会干活的进阶之路
昨天我们把 Linux 环境跑起来,学会了怎么进出目录、怎么看文件,算是正式踏进了这个黑乎乎的命令行世界。今天这篇 day02 笔记,我会延续第一天的节奏,专门梳理那些日常使用频率最高、面试也常被问到的命令和概念,包括用户与权限、进程管理、文本处理、系统状态查看、网络排查,最后再搭配几个我实际工作中踩过的坑和故障案例。学完今天这些内容,你就不再是只会敲ls的小白了,至少能处理一些真实的运维场景,看得懂系统日志,也敢动手去排查问题了。
这篇笔记适合刚学完基础命令、想往运维或嵌入式方向走的同学,也适合那些命令行用了很久但一直靠死记硬背、没搞清楚底层逻辑的朋友。我尽量不堆砌命令列表,而是把每个命令放到实际场景里讲,告诉你为什么用、什么时候用、用了之后看什么。毕竟 Linux 这东西,光背命令是记不住的,你得理解它背后的设计思路——一切皆文件、权限驱动、进程协作,顺着这条线往下走,命令自然就串起来了。
先说一下今天笔记的定位:day02 不碰编译内核、不搞驱动开发,也不深入源码,专注把“日常管理和排障”这层打扎实。具体内容包括用户与文件权限、进程与后台任务、文本与日志处理、系统资源查看、网络工具,以及几个我自己遇到过的真实故障案例。每段我都会给出命令示例和执行思路,你看完可以直接在自己的虚拟机里练一遍。
2. 用户与权限:Linux 安全模型的基石
2.1 用户、组、权限位到底怎么配合
很多新手刚接触 Linux 权限时,最困惑的一件事就是:明明文件是自己的,为什么改了没效果?为什么别人能读我不能读?这背后其实就是 Linux 最核心的安全模型——用户(User)、组(Group)、其他(Other)三级权限体系。
在 Linux 里,每个文件都记着三组权限:属主(owner)权限、属组(group)权限、其他用户(others)权限。每组权限又分读(r=4)、写(w=2)、执行(x=1)三种,数字表示法是它们的和。比如755表示属主可读写执行(7=4+2+1),属组可读可执行(5=4+1),其他人可读可执行(5=4+1)。这套设计相比 Windows 的 ACL 权限要简单直接得多,但同时它也意味着你必须理解文件属主和进程运行用户的关系,否则容易出现权限混乱。
我举个真实场景:在服务器上部署一个 Web 应用,Nginx 进程以www-data用户运行,站点目录属主如果被设成root:root,权限是755,那 Web 进程能读文件但不能写。如果应用需要上传文件,你只改文件权限改成777,当时能写,但这是非常危险的做法,因为任何本地用户都能改了。正确做法是:把目录属主改成www-data,或者把www-data加进目录属组,然后给属组加写权限。
这里我建议你养成一个习惯:拿到任何一个 Linux 系统,第一件事就是用id命令看当前用户和组信息,用ls -l看关键目录的属主属组。理解了这套权限模型,后面配 SSH、配 Web、搞共享目录都会顺很多。
2.2 用户管理常用操作:useradd、usermod、passwd
创建用户是 day02 必须练熟的操作。推荐用useradd加参数的方式,而不是直接改/etc/passwd,虽然直接改文件在某些紧急场景下也能用,但容易出错。
# 创建用户,同时指定 home 目录、登录 shell、附加组 useradd -m -d /home/zhangsan -s /bin/bash -G wheel zhangsan # -m:自动创建 home 目录 # -d:指定 home 目录路径 # -s:指定登录 shell # -G:附加组,可以多个,逗号分隔 # 设置或修改密码 passwd zhangsan # 查看用户信息 id zhangsan这里有个细节:如果你不加-m,很多发行版默认不会自动创建 home 目录,用户登录后进到一个不存在的目录,会出现各种路径问题。另外-G wheel是为了让用户加入管理员组,具体组名看发行版,CentOS/RHEL 系是wheel,Ubuntu/Debian 系是sudo。
删除用户用userdel -r zhangsan,-r会连 home 目录和邮件池一起删,不加-r则只删用户但保留文件。你要是有强迫症,删完用户记得检查一下/etc/passwd、/etc/group、/etc/shadow三个文件里有没有残留条目,避免 uid 复用导致权限串号。
2.3 chmod、chown、chgrp 的实操细节
权限修改三兄弟:chmod改权限位,chown改属主,chgrp改属组。这仨命令看起来简单,但实际使用中有几个坑我踩过好几次。
第一个坑:chown后面是先写属主再写属组,中间用冒号分隔,比如chown root:root file。很多新手容易把顺序搞反,写成chown root:root还好,如果要只改属组,得写chown :group file,注意前面的冒号不能省,省了就会把group当成用户名去解析,直接报错。
第二个坑:递归授权要谨慎。chmod -R 777 /some/dir这种命令,我强烈建议少用。一旦你递归改了系统目录,比如/usr或/var,那系统基本就半残了——某些程序启动时会因为关键文件权限过宽而拒绝运行,更糟的是给恶意程序开了后门。正确做法是分级授权,比如网站目录下,目录用755,普通文件用644,需要写入的上传目录单独设775属组可写。
第三个坑:chmod除了数字法,还有符号法。比如chmod u+x file表示给属主加执行权限,chmod g-w,o-r file表示属组去写、其他去读。符号法在微调权限时比数字法直观,但很多教程不讲,导致新手只会777。实际排障时,符号法非常有用,比如一键给所有 shell 脚本加执行权限:
find /path/to/scripts -name "*.sh" -exec chmod +x {} \;2.4 用 ACL 补足基础权限的短板
基础权限模型的短板在于:一个文件只能有一个属主和一个属组。但实际场景里经常出现“A、B 两个不同组的人都要能读写某个共享目录”的需求,这时候基础权限解决不了,得用 ACL(Access Control List)。
ACL 可以给特定用户或组单独设置权限,而不受传统属主/属组限制。查看 ACL 用getfacl,设置用setfacl:
# 给 lisi 用户针对 /data/share 目录增加读写执行权限 setfacl -m u:lisi:rwx /data/share # 删除某个用户的 ACL 条目 setfacl -x u:lisi /data/share # 递归设置目录及目录下已有文件的 ACL setfacl -Rm u:lisi:rwx /data/share注意:递归-R只对已有文件生效,如果以后在目录里新建了文件,默认不会继承 ACL。要让新文件自动继承,需要设置默认 ACL:
setfacl -m d:u:lisi:rwx /data/share这个d:前缀就是 default 的意思,设置之后,这个目录下新建的文件会自动赋予 lisi 读写执行权限。这个技巧在做团队共享目录、FTP 目录、Samba 共享时非常实用。
3. 进程管理与后台任务:别再用 Ctrl+C 硬扛所有进程
3.1 ps、top、htop 的合适使用场景
进程管理是运维的核心功,不会看进程状态,排查问题几乎寸步难行。
ps是静态快照,适合“看一眼当前有哪些进程”。最常用的组合是ps -ef和ps aux,两者展示的信息略有差别,但基本都包含 PID(进程号)、PPID(父进程号)、CPU%、内存%、启动命令。如果你想找某个特定进程,配合grep是最快的方式:
ps -ef | grep nginx这里有个小技巧:用grep -v grep把 grep 自身那行过滤掉,或者直接用pgrep -af nginx,pgrep 命令就是专门干这个的,更干净。pgrep -f是按完整命令行匹配,而不是只匹配进程名,在找脚本进程时特别管用。
top是动态刷新,适合“持续观察哪几个进程吃 CPU、吃内存”。进去之后按P按 CPU 排序,按M按内存排序,按q退出。如果你在排查内存泄漏,top 的RES列(常驻内存)比VIRT列更有参考价值,VIRT 只是虚拟内存,不代表真实占用。
htop是 top 的增强版,支持鼠标操作、树状视图、直接 F9 杀进程,但很多最小化安装的服务器没有,需要自己装。我的建议是:top 必须熟练,htop 能装就装,毕竟排障时一个高亮彩色界面比满屏黑字舒服得多。
3.2 后台运行:&、nohup、setsid、tmux 一次讲清
新手最常见的困惑是:明明程序在终端里跑得好好的,一关终端窗口程序就死了,这是为什么?
因为大多数程序是终端的前台进程,终端关闭时内核会向会话中的进程发送 SIGHUP 信号,进程没处理这个信号,默认行为就是终止。解决办法就是让进程脱离终端会话。
最简单的是在命令末尾加&,让它在后台运行。但这只解决了“不占你终端”的问题,终端关闭时它依然可能被杀,因为它的会话还是和终端关联。
更可靠的是nohup:
nohup python3 myscript.py > /var/log/myscript.log 2>&1 &拆解一下:nohup让进程忽略 SIGHUP,> logfile把标准输出重定向到文件,2>&1把标准错误也并到标准输出里,最后的&让命令在后台运行。这个命令组合是运维日常最常用的启动姿势,没有之一。
setsid更彻底,它让进程直接成为新的会话首进程,完全脱离终端控制。用法更简单:
setsid python3 myscript.py > myscript.log 2>&1连&都不用加,因为它已经是独立的会话了。但说实话,日常场景里nohup已经够用,setsid我一般只在写服务启动脚本时用。
如果你需要更高级的会话管理,比如关掉终端后过几小时再回来查看程序输出、在远程跑长任务、同事之间共享一个操作会话,那就该上tmux了。tmux 是终端复用器的王者,最核心的概念是“会话(session)”和“窗口(window)”:
tmux new -s work # 新建一个名为 work 的会话 tmux detach # 从会话中分离(相当于挂起),快捷键 Ctrl+b d tmux ls # 查看所有会话 tmux attach -t work # 重新连接 work 会话在 tmux 里跑任务,就算你 SSH 断开,任务照常运行。回来tmux attach就能看到现场,这一点在跑长任务、部署脚本、数据库迁移时真的救命。
3.3 kill、killall、pkill:杀进程的高阶姿势
杀进程不是上来就kill -9。很多新手的习惯是:卡住了就kill -9,结果不仅没解决问题,还可能把数据弄丢。这里必须理解 Linux 信号机制。
kill默认发送 SIGTERM(15),这是“请优雅退出”,程序收到后会清理资源、保存状态、然后退出。kill -9发送 SIGKILL,这是“立刻终止,不容商量”,内核直接回收资源,但程序没机会做任何清理,可能造成文件损坏、数据不一致。
正确流程是:先kill -15 PID,等几秒看进程是否还在,不行再kill -9 PID。当然,对于僵尸进程(Z 状态),kill 是无效的,你只能杀它的父进程让 init 去回收。
killall nginx按进程名杀,pkill -f "python.*server.py"按完整命令行匹配杀,这两者比逐个查 PID 要快得多。但有个大坑:pkill -f太容易被自己误伤——比如你用pkill -f "grep"可能会把pgrep或者pkill自己匹配进去。更安全的做法是,先用pgrep -af看一下匹配到了哪些进程,确认没问题再杀。
3.4 系统服务管理:systemctl 的日常用法
现在的主流发行版都用 systemd 管理服务,service和chkconfig在老系统上还能见到,但新环境基本是systemctl的天下。day02 至少掌握这几个:
systemctl start nginx # 启动服务 systemctl stop nginx # 停止服务 systemctl restart nginx # 重启服务 systemctl status nginx # 查看服务状态,包括 PID、最近日志、活跃状态 systemctl enable nginx # 设置开机自启 systemctl disable nginx # 取消开机自启 systemctl list-units --type=service --state=running # 列出所有运行中的服务systemctl status输出的信息量很大,包括服务当前状态(active/exited/failed)、主进程 PID、最近几条日志。排障时先跑这个,基本能确定服务是被杀掉了、启动失败还是压根没启动。
还有个隐藏技巧:systemctl status是有退出码的。进程正常运行返回 0,异常返回非 0,在脚本里可以用$?判断服务是否健康。比如:
systemctl status nginx > /dev/null 2>&1 if [ $? -ne 0 ]; then systemctl restart nginx fi这就在 shell 脚本里实现了一个极简的保活逻辑。
4. 文本处理三剑客:grep、sed、awk 的实战组合
4.1 grep:不只是简单的关键词搜索
grep 是 Linux 上使用频率最高的命令之一,但很多人只会grep keyword file这种简单用法。稍微深入一点,grep 能做的事情远超你想象。
首先,grep -r可以递归搜索目录,排错日志时特别常用。grep -i忽略大小写,grep -n显示行号,grep -v反选(排除匹配行)。组合起来就是:
grep -rn "ERROR" /var/log/app/ | grep -v "healthcheck"其次,grep -A和grep -B可以在匹配行之后/之前显示指定行数的上下文,看异常日志时非常实用。比如查看报错前后各 5 行:
grep -A 5 -B 5 "OutOfMemoryError" /var/log/app.log还有grep -E支持扩展正则,grep -P支持 Perl 兼容正则。比如查找所有 IP 地址,一条命令搞定:
grep -P -o "\d{1,3}(\.\d{1,3}){3}" access.log | sort | uniq -c | sort -rn这条命令还能顺带统计每个 IP 出现的次数,排在前面的就是访问最频繁的,用来识别异常扫描很有用。
4.2 sed:流编辑器,增删改查一把梭
sed 的核心用法是“流式处理”,它逐行读取文件,对每一行执行替换、删除、插入操作,默认不修改原始文件,除非加-i。
最常用的替换操作:
# 把文件里所有 old 替换成 new,预览到屏幕(不写文件) sed 's/old/new/g' file.txt # 直接修改文件(注意 -i 会覆盖原文件,建议先备份) sed -i 's/old/new/g' file.txt # 只替换每行第 2 次出现的内容 sed 's/old/new/2' file.txt-i后面可以跟备份后缀,比如sed -i.bak 's/old/new/g' file.txt,会自动生成一个file.txt.bak备份文件再改原文件。改系统配置前我强烈建议养成这个习惯,改坏了还能秒回滚。
sed 也可以按行号操作:
sed -n '5,10p' file.txt # 查看第 5 到 10 行 sed '/ERROR/d' file.txt # 删除所有包含 ERROR 的行-n和p的组合就是“只打印匹配的行”,相当于一个更精确的 grep。把所有 Nginx 配置里被注释掉的 server_name 行摘出来,只用一个命令:
sed -n 's/^#\s*server_name\s*//p' nginx.conf4.3 awk:按列处理的王牌工具
awk 是按列处理文本的神器,尤其适合处理日志、表格数据。它的核心思想是:把每一行按分隔符拆成多个字段,然后对字段做处理。默认分隔符是空格,可以用-F指定。
最经典的用法是打印指定列:
# 打印 df 输出中的第 1 列(文件系统)和第 5 列(使用率) df -h | awk '{print $1, $5}'再进阶一点,awk 可以做条件判断和统计:
# 找出磁盘使用率超过 80% 的分区 df -h | awk 'NR>1 && +$5 > 80 {print $1, $5}' # 统计日志中 500 错误的数量 awk '$9 == 500 {count++} END {print count}' access.logawk 最大的价值在于它本身就是一门小语言,有变量、循环、数组。比如统计访问量 Top 10 的 URL:
awk '{count[$7]++} END {for (url in count) print count[url], url}' access.log | sort -rn | head -10这条命令的意思:对每一行,用第 7 列(URL)做 key,给对应的计数器加 1。文件处理完后,遍历所有 URL 打印计数,再用sort -rn倒序排序取前 10。这放在任何日志分析工具里都是核心逻辑。
4.4 组合实战:从日志里快速定位故障现场
三个工具单独用都简单,组合起来才见功力。举个例子:假设你是 Java 应用运维,应用日志打印格式是2025-01-15 10:22:33 ERROR - NullPointerException,你要统计当天每个小时出现 ERROR 的次数,然后找出次数最多的那一个小时出现在哪些行。
第一步,用 awk 提取小时字段并按小时分组统计:
awk '{hour=substr($2,1,2); if ($3=="ERROR") count[hour]++} END {for (h in count) print h, count[h]}' app.log | sort -k2 -rn第二步,找到最多的小时,假设是14,再筛出那一小时内所有 ERROR 的完整日志:
grep " 14:" app.log | grep "ERROR"第三步,如果信息还不够,用 sed 把错误前后的上下文捞出来:
grep -n "NullPointerException" app.log | head -5 | awk -F: '{print $1}' | xargs -I{} sed -n '{},{}p' app.log这三板斧打完,问题基本就能定位到具体代码行或者具体输入参数了。这套组合拳在没有任何日志分析平台的裸环境下,就是我处理问题的主要手段。
5. 系统状态与资源查看:知己知彼,百战不殆
5.1 df、du 与磁盘占用排查
磁盘满是最常见的故障之一,也是最容易排查的。df -h看文件系统整体使用率,du -sh看目录总共多大,du -h --max-depth=1看一层目录的大小分布。
排查“/ 分区红了但不知道什么占的空间”的标准流程:
df -h # / 分区 100% 满 cd / du -h --max-depth=1 | sort -rh | head -10 # 找到 /var 占最大 cd /var du -h --max-depth=1 | sort -rh | head -10 # 再往下钻到 /var/log,找到几个巨型日志文件这个过程就像剥洋葱,一层层找到最大的目录,最后一层定位到大文件。我经常遇到的情况是:某个进程持续输出日志,日志文件不断增长,几分钟就把磁盘写满。这时候除了删日志,更重要的是搞清楚为什么日志会爆发式增长——多半是应用进入了死循环或者报错刷屏。
还有个隐藏技巧:用lsof | grep deleted找出“已被删除但仍有进程占用”的文件。这种文件不占目录空间,但占磁盘空间。如果删了文件磁盘空间没释放,大概率就是这个原因。比如网站上传的临时图片被删除后,如果 Nginx 进程还在写这个句柄,文件占的空间就一直在。用lsof找到 PID 再重启那个进程,空间才会真正释放。
5.2 free、vmstat、iostat 读懂内存与 IO
free -h看内存使用。很多新手看到free里 avail 值小就急着加内存,其实这是误区。Linux 的内存策略是“能用就用”——内存空闲着也是浪费,不如拿去缓存磁盘块。所以看内存压力不能只看 free 列,要看available列(真实可用的内存),以及swap的使用情况。
如果 swap 持续增长,说明物理内存真的不够了。排查到底谁在吃内存,用top按内存排序,或者用ps快照:
ps aux --sort=-%mem | head -10vmstat更适合看整体趋势。vmstat 1每秒刷新一次,重点关注r(运行队列)、b(阻塞进程数)、si/so(换入换出)这几列。如果r长期大于 CPU 核数,说明 CPU 过载;如果si/so常年不为 0,说明内存压力很大,系统正在频繁换页,这时候加内存比调优来得更实在。
iostat看磁盘 IO,重点看%util和await。%util接近 100% 说明磁盘已成为瓶颈,await过长说明 IO 响应慢。遇到数据库慢查询排查时,iostat 能帮你判断是 SQL 的问题还是磁盘硬件的问题。
5.3 系统时间与时区管理
时间同步是运维里一个看似简单、坑却很多的点。如果服务器时间和真实时间差了太多,会导致日志时间错乱、HTTPS 证书校验失败、数据库主从同步异常等一系列问题。
查看时间:
date # 系统时间 date -R # 带时区显示,比如 +0800 timedatectl # 查看系统时间、时区、NTP 同步状态修改时区:
timedatectl set-timezone Asia/ShanghaiNTP 同步:
# 手动同步一次 ntpdate -u ntp.aliyun.com # 老工具,很多新系统已移除 # 推荐用 chrony systemctl restart chronyd chronyc sources -v # 查看 NTP 同步状态如果发现服务器时间一直不准,优先检查 chronyd 或 ntpd 是否运行、是否能连通 NTP 服务器。防火墙挡了 UDP 123 端口是一个常见原因。
5.4 网络查看与连接排查
网络排查在 day02 先掌握最基础的工具组合。ip addr看网卡 IP,ip route看路由,ping看连通性。如果要监听的端口被占了,用ss或旧的netstat:
ss -tlnp # 查看所有监听的 TCP 端口及对应进程 ss -antp # 查看所有 TCP 连接状态排查“端口起不来”的标准姿势:
ss -tlnp | grep :8080 # 8080 是否已被别的进程占用如果是服务器的端口明明通了但外部访问不了,那大概率是防火墙问题。用firewall-cmd --list-all或ufw status看规则,再决定放行还是关掉。
这些都是排障最基础的工具,但要真正用好,得学会看输出的每一列代表什么。比如ss -antp输出里有LISTEN、ESTAB、TIME_WAIT等状态,TIME_WAIT大量堆积说明连接关闭频繁,多半是短连接过多,这时候调应用的连接池比关防火墙更有效。
6. 环境变量与 Shell 脚本入门:让命令自动化起来
6.1 环境变量到底是怎么生效的
环境变量是 Shell 和程序之间传递配置信息的通道。echo $PATH能看当前 PATH,export PATH=$PATH:/new/path能临时追加。但这里有个新手容易混乱的点:export只对当前 Shell 会话有效,关闭终端就没了。
要让环境变量永久生效,需要写进配置文件。不同发行版、不同 Shell 的配置加载顺序不一样,是新手很容易懵的地方。以 bash 为例,登录 shell 会依次加载/etc/profile、~/.bash_profile或~/.bash_login、~/.profile,非登录交互 shell 则加载~/.bashrc。CentOS 上通常会在~/.bash_profile里手动 source~/.bashrc,而 Ubuntu 则是/etc/profile里 source/etc/bash.bashrc。
我个人的建议是:把自定义 PATH 和别名写到~/.bashrc底部,因为非登录 shell(比如在 tmux 里新开窗口)也会加载它。写完后执行source ~/.bashrc立即生效,不用重新登录。像 Anaconda 的环境变量,装完之后提示你写进~/.bashrc,就是这么个道理。
6.2 自定义 PATH 与 JAVA_HOME 这类典型配置
在命令行里执行java -version时,Shell 其实是在 PATH 里找名为 java 的可执行文件。如果你装了 JDK 但没配 PATH,Shell 就找不到,直接报command not found。
典型配置方式是在~/.bashrc里:
export JAVA_HOME=/opt/jdk17 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib注意 PATH 的赋值顺序:$JAVA_HOME/bin放在最前面,意思就是优先使用我们自己装的 JDK,而不是系统自带的 OpenJDK。这个“越靠前越优先”的规则,在排查“明明装了新版本但命令还是老版本”的问题时是关键。
检查是否生效:
which java java -version echo $PATH6.3 第一个实用脚本:日志清理脚本
环境变量和命令掌握后,写一个简单的日志清理脚本是把知识串起来的最佳练习。我随便写一个监控并清理超过 N 天日志的脚本:
#!/bin/bash # 清理超过 7 天的日志文件 # 先打印将被删除的文件,人工确认后再取消注释执行删除 LOG_DIR="/var/log/myapp" DAYS=7 find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -print # find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -delete这个脚本有几个值得说的点。-mtime +7表示修改时间在 7 天之前的文件,-type f限定只找普通文件,避免误删目录。先打印再删是我强烈建议的保险策略,生产环境直接删文件的脚本一定要先验证一遍输出结果。
更稳健的脚本会考虑磁盘占用率作为触发条件:
#!/bin/bash LOG_DIR="/var/log/myapp" THRESHOLD=80 usage=$(df -h "$LOG_DIR" | awk 'NR==2 {print +$5}') if [ "$usage" -gt "$THRESHOLD" ]; then find "$LOG_DIR" -name "*.log" -type f -mtime +3 -delete echo "磁盘使用率 ${usage}%,已清理 3 天前的日志。" else echo "磁盘使用率 ${usage}%,无需清理。" fidf -h输出的第五列是使用率百分比,awk+$5把它转成数字参与比较。这个脚本加进 crontab 里每天执行一次,就能实现日志自动轮转,算是最简单的运维自动化实践。
6.4 crontab 调度任务入门
cron 是 Linux 自带的定时任务工具,crontab -e编辑当前用户的定时任务。格式是五个字段:分、时、日、月、周。举个例子:
# 每天凌晨 2 点执行备份脚本 0 2 * * * /home/user/bin/backup.sh # 每 5 分钟检查一次服务状态 */5 * * * * /home/user/bin/check_service.sh # 每周一早上 8 点清理临时目录 0 8 * * 1 /usr/bin/find /tmp -type f -mtime +7 -delete配置好之后,日志会写到/var/log/cron或通过journalctl -u crond查看。如果定时任务没有执行,先排查 crond 服务是否在跑,再看脚本有没有执行权限、有没有写执行日志。
这里提醒一个常见的坑:cron 执行环境和我们手动登录终端的环境不一样,它没有加载~/.bashrc,所以脚本里如果用到了自定义 PATH 或者 JAVA_HOME,必须在脚本开头显式设置,否则就会出现“手动执行脚本没问题,但 cron 里跑就报 command not found”的诡异现象。
7. 常见故障排查实录:这些问题我踩过,你们别再踩了
7.1 故障案例 1:磁盘空间满了,但是删了文件还没释放
现象:df -h显示/分区使用率 100%,但du -sh /算下来远没到那么大。于是你以为自己算错了,但实际是文件系统上有文件被删除了,但还有进程持有它的文件句柄。
排查流程:
df -h du -sh /* # 找不到大头 lsof | grep deleted # 发现 /var/log/nginx/access.log (deleted) 还被 nginx 进程占用 systemctl restart nginx df -h # 空间释放了这类问题最常见的场景就是日志文件:你rm了日志,但写入日志的进程还在运行,这个文件的实际数据块一直没释放。记住:删除文件不等于释放磁盘,还要确保没有进程继续持有该文件的句柄。
7.2 故障案例 2:程序一关终端就“消失”
现象:在远程 SSH 终端里启动了一个 Java 程序,一切正常。退出 SSH 再重连,发现进程没了,日志里也没有明显报错。
原因就是终端关闭后 SIGHUP 把进程带走了。解决办法就是用前面讲的nohup或setsid。更规范的做法是写成 systemd service,让 systemd 管理进程生命周期,这样不仅能开机自启,还能崩溃自动重启:
[Unit] Description=My Java App After=network.target [Service] Type=simple User=appuser ExecStart=/usr/bin/java -jar /opt/app/myapp.jar Restart=on-failure RestartSec=5 Environment=JAVA_HOME=/opt/jdk17 [Install] WantedBy=multi-user.target把文件放到/etc/systemd/system/myapp.service,然后:
systemctl daemon-reload systemctl enable --now myapp7.3 故障案例 3:明明有权限,但脚本执行报 Permission denied
现象:写了个deploy.sh,ls -l看权限也正常,但执行./deploy.sh就报Permission denied。
这个问题的原因多半是脚本没有执行权限。解决:
chmod +x deploy.sh ./deploy.sh另一个变种是“执行到一半报 Permission denied”,那是脚本内部某个命令的执行权限不足,或者脚本尝试往没有写权限的目录写入文件。排查方式是把脚本一行行拆开执行,或者用bash -x deploy.sh开启调试模式,它会一条条打印执行的命令和结果,很容易定位是哪一步出错。
7.4 故障案例 4:端口被占用,服务起不来
现象:启动 Nginx 报错bind() to 0.0.0.0:80 failed (98: Address already in use)。
排查流程:
ss -tlnp | grep :80 # 看到 PID 后确认是哪个进程占着 80 端口 ps -p PID -o pid,comm,args # 如果是旧版 nginx 没杀干净 kill -15 PID # 还不退就 kill -9 systemctl start nginx这里给一个容易踩坑的经验:如果端口上跑的是一个重要的线上服务,杀之前一定要确认清楚。有一次我遇到过 80 端口被一个异常启动的 Java 测试进程占了,杀完之后发现那竟然是另一个部门在用的内部服务,搞得大家虚惊一场。现在我的习惯是:杀进程前一定先ps -p看启动命令、看启动时间,确认是预期内的进程才动手。
7.5 故障排查速查表:对照这表能解决八成日常问题
| 现象 | 可能原因 | 排查命令 | 解決思路 |
|---|---|---|---|
| 命令找不到 | PATH 没配或软件没装 | echo $PATH,which cmd | 重装或用绝对路径执行 |
| 磁盘满 | 日志爆炸或大文件残留 | df -h,du -sh /*, `lsof | grep deleted` |
| 内存不足 | 应用泄漏或缓存太多 | free -h,top -o %MEM,ps aux --sort=-%mem | 重启应用或加内存 |
| 端口被占用 | 进程残留或冲突 | ss -tlnp | 确认进程后 kill,或用其它端口 |
| 服务起不来 | 配置错误或依赖缺失 | systemctl status -l,journalctl -u 服务名 -e | 看日志定位,修复配置 |
| 关终端进程就没了 | 未脱离终端会话 | 检查父进程,nohup启动 | 用 nohup / systemd 管理 |
| 文件删了空间没释放 | 有进程持有句柄 | `lsof | grep deleted` |
| 时间不准 | NTP 失效或时区错误 | timedatectl,chronyc sources | 设置时区,重启 chronyd |
| cron 任务不执行 | 环境变量缺失或服务没启动 | journalctl -u crond,crontab -l | 脚本内显式设置 PATH |
| 网络不通 | 防火墙或路由问题 | ping,ip route,firewall-cmd --list-all | 按链路逐层排查 |
8. 学习建议与扩展路径
day02 的内容量其实不小,从用户权限到进程管理,从文本处理到故障排查,每块都能展开成一篇独立的文章。我在实际学习时的一个体会是:不要追求“把所有命令背下来”,而是要追求“遇到问题能想到用什么工具”。命令是工具,场景化学习比死记硬背高效得多。
一个比较有效的方法是:在虚拟机里故意制造故障,然后自己排查。比如把 nginx 的端口改成 90,然后启动看报错;比如故意删掉一个系统日志文件,然后观察磁盘占用;比如写一个不断往内存写数据的 Python 脚本,然后用top观察系统怎么崩。这些“搞破坏”的练习,才是真正把知识变成能力的过程。
这套笔记之后可以往几个方向扩展:一是 Shell 脚本编程深入,把循环、函数、判断都用熟,写出一套完整的自动化脚本;二是服务部署实战,比如用 systemd 管理 Nginx、MySQL、Java 应用,搞明白服务开机自启的完整链路;三是网络进阶,把 tcpdump、iptables、路由策略这些搞明白,能独立排查跨服务器通信的问题。每个方向都能让 day02 这些基础发挥出指数级的价值。
最后说一个我个人的建议:日常工作中遇到不认识的命令,先用man查手册,再看/usr/share/doc里的示例,最后再上网搜。搜索引擎能解决 80% 的问题,但这 80% 里有一半是英文资料,所以英文阅读能力对 Linux 学习来说,是越早开始练习越好的投资。