凌晨两点半,某台业务服务器的磁盘空间告警钉钉一直在响。我当时第一反应就是先登上机器执行df -h,紧接着du -sh /data/*一层层剥开目录,最后定位到某个应用日志目录下躺着一个 60 多 G 的 debug 日志文件,从文件末尾时间戳往前一推,发现已经连续写了一个半月没人管。整个过程不到三分钟,靠的正是几条再普通不过的 linux 命令。
linux命令这东西,说简单真不简单——它不是靠背出来的,而是靠一次次真实排查、处理问题锤出来的。网上各种命令大全、手册一搜一大把,但真遇到问题的时候,能条件反射般组合起来救命的,永远是你真正用过、踩过坑的那几条。
这篇文章我不打算再罗列什么“常用命令清单”,那玩意儿你收藏了也不会看。我想换个角度,专门聊聊命令行背后的底层逻辑、排查链路,以及我在真实环境里总结出来的一些经验和教训。无论你是刚入行的运维、被服务器折腾的后端开发,还是想系统提升效率的测试同学,这篇内容应该都能让你少走一些弯路。
1. 背了几百条命令还是不会用——问题不在记忆力
很多朋友学 linux 命令时有个误区:买了一堆书,或者收藏了无数篇“必学命令大全”,把ls、cd、cat、grep、awk、sed背得滚瓜烂熟,但真到了服务器上,脑子依然一片空白。
这不是记忆力的问题,而是学习路径的问题。命令本身不是一个独立的知识点,它是你解决问题的工具。就像你家里的工具箱,螺丝刀再好,你不会拆水龙头,照样修不了漏水。
1.1 用场景驱动代替参数堆砌
正确的方式是“场景驱动”。你不需要记全一条命令的三十个参数,你需要的是:在什么场景下,用哪几条命令组合起来,能最快定位和处理问题。
举一个很典型的例子。有个同事问我说:“为什么我的tar命令解压以后,文件都跑到当前目录外面去了?”我看着他说:你八成是没加-C指定目标目录,解压出来的相对路径直接撒在根目录下了。这个问题不是命令记不住的问题,而是他根本没意识到“解压到指定目录”本身是一个高频场景,应该养成本能。
我的建议是每次学命令前都先问自己四个问题:
- 这个命令解决的是哪一类问题?(磁盘、网络、进程、日志?)
- 它最常用的输出里有哪几个关键字段?(别全看,只看重点)
- 它和哪几个命令通常会配合使用?
- 我上次遇到类似问题的时候,是怎么排查的?
当你用这种方式去“攒经验”,你会慢慢建立起一套自己的排查习惯,而不是每次遇到问题都在搜索引擎里捞命令。
1.2 一条命令能搞定的事,别用两条
很多新手喜欢“表演式”写命令——用一堆管道、复杂参数,看起来很高大上,其实效率反而更低。经验老到的工程师通常反着来:先求能跑通,再求简洁。
比如想找/var/log下今天修改过且超过 100M 的日志文件,新手可能会说:
find /var/log -type f -name "*.log" | xargs ls -l | awk '$5 > 104857600 {print $9}'看着很炫,但有一个明显的问题:一旦文件名里有空格,xargs ls这层就能把整个命令链带崩。换成工程师常用的写法:
find /var/log -type f -name "*.log" -size +100M -mtime -1 -printf "%s %p\n"一招到位,还不用走管道,也没空格问题。这个差异说明一条核心原则:命令越少越好,管道越短越好,每一步都要考虑容错。
所以在正文开始之前,先把这个底层思路明确下来。接下来我分享的所有排查链路,都是从这个思路出发设计的。
2. 磁盘告警排查链:从告警短信到揪出大文件的完整动线
磁盘满是个特别经典也非常高频的问题。开头提到的凌晨告警,其实只是日常一景。很多人在df -h看到100%之后就开始慌了,看哪哪都像是罪魁祸首,最后一顿乱删,甚至误删了系统文件。这种时候最忌讳的就是“乱”,得有条理地一层层剥下去。
2.1 先用 df 看整体,分清楚是哪一层满了
第一步永远是df -h,看整个文件系统的挂载点和使用率:
df -h输出大致长这样:
文件系统 大小 已用 可用 已用% 挂载点 /dev/vda1 20G 19G 1.2G 95% / tmpfs 3.9G 2.1G 1.9G 54% /dev/shm /dev/vdb1 200G 150G 50G 75% /data看到/dev/vda1 95%,就明确了两件事:一是要清理的分区是根分区;二是这台机器的数据盘/data是独立的,磁盘空间告警很可能和业务数据没关系,而是系统盘里的小文件占了地方。
这里有个新手容易忽略的坑:df -h默认不显示 inode 使用情况。文件系统满了不只是“容量满了”,还有一种情况是“索引节点满了”,也就是小文件数量太多,每个文件都会消耗一个 inode,把整个文件系统的 inode 耗尽后,系统同样会提示“No space left on device”,哪怕可用容量还有很多。
遇到这种怪现象,执行:
df -i如果 IUse% 接近 100%,那问题就变成了“找哪来的一堆小文件”,而不是“哪来的大文件”。处理方式侧重点完全不同。
2.2 用 du 顺藤摸瓜,从大到小找目录
确认磁盘满了之后,核心目标就是“找到大目录”。我习惯的做法是从根目录开始逐层往下“砍”:
du -sh /* 2>/dev/null | sort -hr这条命令的作用是列出根目录下每个一级目录的占用大小,并排序,2>/dev/null 把权限拒绝无所谓的报错丢到一边,不打断输出。
输出里一眼能看到占用最大的目录,比如/var占了 10G。接着继续:
du -sh /var/* 2>/dev/null | sort -hr依次往下,三级、四级目录一层层定位。这个“逐层剥洋葱”的过程看起来笨,但它是最稳的排查方式。生产环境里别用du -h /var这样的写法去扫整个目录树,目录一深、文件一多,du半天跑不完不说,还会额外消耗磁盘 IO,直接把你正在排查的服务器拖得更慢。
| 命令写法 | 适用场景 | 风险 |
|---|---|---|
du -sh /var | 只看总量 | 无大额外耗时 |
du -h /var | 想看全部子目录 | 文件多时非常慢 |
du -h --max-depth=1 /var | 只在快速定位下一层 | 折中方案,推荐 |
用--max-depth=1而不是全量递归,是经验之谈。这样既不用一层层重复敲命令,又不会让du因为递归过深而卡死。
2.3 定位到文件后,别急着删,先确认是什么在写
假设你最终找到了一个 60G 的日志文件/data/app/log/app.log。这时候直接rm掉可能暂时解决问题,但你会发现过几天又满了。因为写这个日志的服务还在运行,文件句柄依然被进程持有,日志会继续往那个已被删除的文件里写,磁盘空间根本释放不出来。
正确做法是先看一眼谁在写它:
lsof +L1 | grep app.log这条命令会列出所有已经被删除到只剩下句柄的文件,以及持有它的进程。看到对应 PID 之后,再决定是重启服务让句柄释放,还是先停服务、清空文件、再启动。
如果服务负载高不能随便重启,另一个替代方案是:
cat /dev/null > /data/app/log/app.log相当于把文件内容直接清空而不释放文件本身,正在写的进程毫无感知,空间却立刻释放了。
2.4 查找大文件的高效配方
如果定位方向已经明确,目标就是“找某个目录下超过 1G 的文件”,用find直接搜:
find /data -type f -size +1G -exec ls -lh {} \; 2>/dev/null把-size +1G换成-size +500M、-size +100M可以自由调整阈值。注意-exec ls -lh {} \;结尾的分号在 bash 里需要转义,如果不加,find 会报语法错误。这台命令能在几秒内把目标目录下所有庞然大物列出来,比用du一层层找快得多。
磁盘排查到这里,一个大致的完整链路就清晰了:
df -h判断容量还是 inode 问题。du --max-depth=1逐层找大目录。find -size +1G直接定位大文件。lsof +L1发现被删除但仍占空间的句柄。- 根据实际情况选择清空、重启或删树。
这一套流程走下来,效率极高,也极少误伤。磁盘空间告警几乎每天都有,但真正因为不会排查而把服务器搞挂的案例,我见过太多了,说到底就是没有建立起“从整体到局部”的排除思路。
3. 服务器变慢时的三板斧:进程、内存、IO 的组合排查
如果说磁盘告警是“显性病”,那服务器突然变慢就是“隐性病”,查起来更费劲。一个进程把 CPU 打满、内存吃光、或者磁盘 IO 飙升,症状都一样:用户访问变慢,接口超时。但病因千差万别。
我遇到过不少开发同学,一上来就top,看到某个 CPU 100% 就断定“这个进程出问题了”,其实未必。也可能是这个进程在等待 IO,CPU 空转把负载拉高了。得靠一套组合诊断才能下结论。
3.1 用 ps 找“嫌疑进程”,而不是用 top 翻来翻去
第一板斧,我喜欢用ps而不是top来快速抓嫌疑对象:
ps aux --sort=-%cpu | head -n 15这行命令把当前所有进程按 CPU 占用排序,直接取前 15 个。输出里的关键字段不需要全看,重点关注:
%CPU:占了多少 CPU%MEM:占了多少物理内存STAT:进程当前状态
STAT里的状态字符很多人不理解,简单列几个常见值:
| 状态字符 | 含义 | 常见场景 |
|---|---|---|
| R | 正在运行或可运行 | 高负载计算任务 |
| S | 可中断睡眠 | 正常的等待事件 |
| D | 不可中断睡眠 | 通常意味着等待磁盘 IO |
| Z | 僵尸进程 | 父进程未回收子进程资源 |
| T | 停止 | 被信号暂停 |
看到D状态,就要怀疑是不是磁盘 IO 出了问题;看到Z,重点就得放在它父进程上,看看是不是父进程有 bug 没接住子进程的退出。
3.2 top 的交互操作远比你想象的强大
ps抓到了头号嫌疑进程的 PID,接下来用top -p PID单独盯着它看:
top -p 12345进入交互界面后,有几个按键必须会:
P:按 CPU 占用排序(大写)M:按内存占用排序(大写)k:杀掉选中进程c:显示完整命令行H:切换线程视图
按c展开完整命令行特别实用。有一次我在某台机器上看到有个进程 CPU 一直波动,但它的命令名很正常,后来按下c才发现启动参数里挂了个可疑的脚本路径。很多时候,问题不在进程名,而在于启动参数里藏着的脚本或配置。
3.3 free 的输出要会翻译,Buffer/Cache 不是“已用”
内存问题是新手的重灾区。每次一看free -h发现可用内存只有几百兆,就急得想去 kill 进程。实际上free的输出要正确理解:
free -h输出中available这一列才是真正可用的内存,它已经把可回收的 cache 算进去了。Linux 内核的设计理念是“闲着也是闲着”,空闲内存会尽量用于缓存文件数据,以此来提升读写性能。当你看到内存“被占满”,其实很大一部分是 buffer/cache,它们会在应用真正需要内存时自动释放。
真正该注意的是 swap 的使用情况。如果si/so在持续增长,说明内存真的不够用了,系统在频繁换页,这种场景下面的性能会严重下降。
3.4 用 vmstat 看“整体忙不忙”和 IO 是否瓶颈
ps、top是把镜头对准单个进程,vmstat则是看全局:
vmstat 2 5意思是每隔 2 秒采样一次,一共采 5 次。输出重点看三块:
r:运行队列长度,持续大于 CPU 核心数说明 CPU 忙不过来wa:CPU 等待 IO 的时间占比,明显偏高说明磁盘是瓶颈us、sy:用户态和内核态 CPU 占用
有一次某台机器wa高达 30%,但每个进程 CPU 占用都不高,这就是典型的磁盘 IO 瓶颈。当时查下来是一个业务在批量导入数据,把磁盘打满了,通过iostat -x 1能看到对应的/dev/vdb1的%util接近 100%。这种情况下你哪怕把进程 kill 了,其他进程也一样跑不快,因为根子在磁盘。
所以这“三板斧”的正确使用顺序是:ps找嫌疑进程 →top看细节 →free+vmstat判断是内存、CPU 还是 IO 的问题。顺序别乱,一乱就容易误判。
4. 日志分析里的组合拳:grep、awk、sed 怎么配合出奇迹
聊完系统问题,再聊一个更高频的场景——分析日志。无论你是做后端、运维还是数据分析,只要和服务器打交道,日志就是第一手的真相来源。
但日志文件动辄几百 MB、几个 G,用鼠标双击打开是不可能的。这时候grep、awk、sed这三把刀就能派上用场。
4.1 grep:先把需要的行捞出来
grep的核心作用只有一个:按条件过滤行。最基本的用法是搜关键词:
grep "ERROR" /data/app/log/app.log但生产环境里的日志格式通常很规整,比如:
2025-06-12 10:23:45 ERROR [OrderService] 下单失败: timeout 2025-06-12 10:23:47 INFO [UserService] 查询用户成功 2025-06-12 10:24:01 ERROR [PaymentService] 支付回调超时: 3000ms想找出所有下单失败的记录,可以直接组合多个关键词:
grep "下单失败" app.log想同时排除掉某个干扰关键词,用-v反向过滤:
grep "ERROR" app.log | grep -v "健康检查"想不区分大小写,加-i。想做正则,加-E。日常高频用的无非这几个参数,别去死记那种几十种参数的表格,工作中用到哪个查哪个,形成条件反射的始终是最常用的那几种。
4.2 awk:按列提取和简单统计
grep输出的是整行,有时候我们需要“只取某个字段”。用awk按空格或制表符分割就能轻松实现:
awk '{print $1, $2, $4}' app.log但更让我觉得惊艳的是 awk 的统计能力。假设日志每行末尾都记录了一个耗时毫秒数,像这样:
2025-06-12 10:23:45 INFO [OrderService] 下单成功 235ms 2025-06-12 10:24:01 INFO [OrderService] 下单成功 892ms 2025-06-12 10:25:12 INFO [OrderService] 下单成功 456ms想统计所有“下单成功”的平均耗时,一行搞定:
grep "下单成功" app.log | awk '{sum += $7; count++} END {printf "avg=%.2fms, total=%d\n", sum/count, count}'先grep把行筛出来,再awk把第 7 列的值累加,最后在END里求平均值。如果你只想看耗时超过 500ms 的慢请求,再加个条件:
awk '$7 > 500 {print $0}' app.logawk 语句里的$NF代表最后一列,用$(NF-1)可以取倒数第二列。这个比死记住“第7列”更灵活——日志格式一旦变动,列数不会跟着乱套。
4.3 sed:批量替换和不忍直视的危险操作
sed是处理行的利器,最常见的用法是替换:
sed 's/ERROR/WARN/g' app.log但这只是把替换结果显示在屏幕上,并不会修改原文件。想真正改文件,加-i:
sed -i 's/ERROR/WARN/g' app.log这里必须说一个血泪教训:-i在线上环境一定要慎用,最好先用不带-i的命令把结果预览一遍,确认没问题再加-i。如果非要原地替换,建议后面加个.bak先备份:
sed -i.bak 's/ERROR/WARN/g' app.log这样执行后会自动生成一个app.log.bak,万一替换错了还能救回来。
另外,sed还有一个高频场景是删除指定行的内容,比如删掉日志里前 100 行:
sed '1,100d' app.log注意我写的是'1,100d',不是'1,100 h'——d代表删除,很多新手会把 d 和 h 弄混,导致输出一堆空行。
4.4 三合一:日志聚合分析的一条龙配方
实际工作中,单用一个工具往往不够痛快,把它们组合起来能解决很复杂的需求。
比如我想从一段日志里分析出:“每天哪个接口的平均响应时间最长?”可以这样操作:
grep "接口" app.log | awk '{print $6, $NF}' | awk -F'ms' '{print $1}' | awk '{time[$1] += $2; count[$1]++} END {for (api in time) print api, time[api]/count[api]}' | sort -k2 -nr | head -n 10这一串看着多,其实是把一个复杂的分析拆成了四步:
grep "接口"捞到所有相关日志行。- 第一段先取出接口名和耗时字段。
- 把耗时字段结尾的
ms去掉变成纯数字。 - 按接口名分组求和、计数、算平均,最后按平均值降序取前 10 名。
日常分析接口性能、定位慢查询、统计订单失败量,几乎都能用这套组合拳搞定。学习这些命令的重点不在于记住复杂写法,而在于培养一种“结构化拆解”的能力——先把需求拆成几段,每段交给最擅长的那条命令去完成。
5. 命令越熟练,手越要稳:权限、软链与“慢操作”哲学
如果说前面几节是讲“怎么查”,那这一节讲的是“怎么改”。查错了顶多白费功夫,改错了就有可能导致线上事故。干了这么多年,我对“手稳”这件事的理解,比什么都深。
5.1 chmod 和 chown 容易踩的坑
权限操作最容易踩的坑是“给了不该给的权限”和“把权限改成了错误的所有者”。
我们常说 755、644,很多新手会问这串数字到底是什么。简单解释:
- 权限分三段:所有者、所属组、其他人
- 每段的值是 r(读)=4、w(写)=2、x(执行)=1 之和
所以 644 对文件来说是rw-r--r--,即所有者可以读写,组和其他人只能读;755 对目录来说是rwxr-xr-x,即所有者有完全权限,其他人只能进入和查看,不能创建或删除文件。
写代码和部署脚本时,最安全的原则是:尽量给最小的权限,目录用 755,文件用 644,只有明确需要执行的文件才加 755,别动不动chmod -R 777。真实线上环境里,我见过有人图省事给 Web 目录递归赋了 777 权限,结果被黑客上传了恶意脚本,整个站被人提权,教训极其惨痛。
chown同样是高风险区。把某个文件的所有者改错了,可能导致服务启动失败或者没有权限写日志。一个稳妥的检查方式是改完立刻用ls -l验证:
ls -l /data/app/config.yml别小看这一条验证命令,它可以避免掉 90% 以上的“权限改错后半天找不到原因”的窘境。
5.2 软链接:部署回滚的保命符
ln -s是一条很简单的命令,但用好它,能在发布时少挂好多次。
我们的常规做法是发布系统时建好目录结构:
ln -s /data/app/releases/current /data/app/current代码都在releases/20250612这种带时间戳的目录里,current始终是指向最新版本的软链接。发布新版本时,先复制完整代码到新目录,然后重新指向current,再配一个验证流程。
一旦新版本出了问题,回滚只需要一行:
ln -sfn /data/app/releases/20250530 /data/app/current把软链接重新指回上一个版本目录,服务立刻恢复到旧版本。这里的-f是强制替换,-n是防止在已有目录链接的情况下产生嵌套链接,必须带上,否则第一次能成功,第二次会把链接变成一个目录嵌套,导致整个发布工具被玩坏。
这招对发布系统、配置文件切换、环境变量管理几乎是万能灵药。遇到需要“切换版本”的场景,优先考虑软链接,而不是直接覆盖文件。
5.3 rm 的自我修养:别练“快”,要练“慢”
聊权限和链接,自然绕不开删除。rm -rf是很多人的噩梦,也是每个运维说起来都后背发凉的命令。
我最想强调的一点是:在线上环境里,不要追求删得快,要追求删得稳。我见过不少年轻人用rm -rf /data/app/old/很顺手,结果某次把路径敲成了rm -rf /data/app/,整个应用目录灰飞烟灭。
我自己养成的习惯是:
- 先
ls确认要删的路径内容对不对。 - 输入完路径之后,把整条命令念一遍,屏幕上的路径和脑中的意图对上再回车。
- 第一次删东西时,先加个
-i交互确认,让系统逼你再想一遍。 - 能“移动”就不要“删除”,改成
mv到回收目录,等几天确认没问题再清理。
mv /data/app/old /data/app/trash/20250612这条命令和rm -rf相比只是一个字母的差别,但容错率高得多。犹豫的时候就选mv,确定不要了才rm。听起来很啰嗦,但踩过坑的人都知道,所有的事故,都起源于手比脑子快。
6. 让命令“长在手上”:别名、历史记录与自定义函数
前几节讲的都是单次执行的技巧,真正让 linux 命令变成自己“贴身工具”的,是环境里的习惯沉淀。我见过一些效率极高的同学,他们在终端里几乎不重复输入超过两个字的常用命令,因为在配置层面早就把这些事情解决了。
6.1 alias:把长命令缩成一个单词
最常用的是配置别名。打开~/.bashrc,加几行:
alias l='ls -lh' alias ll='ls -alF' alias du='du -sh' alias untar='tar -zxvf' alias rmgit='git clean -fd' alias dd='df -h'保存后执行source ~/.bashrc生效。从此以后,输入l就能看到人类可读的详情列表,输入dd就能看磁盘。别小看这些小节省,一次少敲几个字母,一天能省几百次键盘敲击,长期积累下来的效率提升相当可观。
定义别名时有一个原则:别覆盖系统自带命令的原有意涵。比如别把cp别名成cp -i,虽然看起来更安全,但某天你在脚本里用cp时,意识不到它背后还带着-i,就会多出大量无谓交互,甚至脚本卡死。要方便就另起一个名字,别去污染原命令。
6.2 history:让历史记录变成你的第二大脑
history命令太强了,很多人却只用它来看刚才打了什么。
我最常用的四个操作:
!!:执行上一条命令!$:上一条命令的最后一个参数!string:执行最近一条以 string 开头的命令Ctrl+R:反向搜索历史命令
比如刚才执行了kill -9 12345,想再杀一个 12346,可以这样:
kill -9 !!:k这个组合看起来高级,但日常更实用的操作是Ctrl+R反向搜索。输入几个字母,终端会帮你匹配历史记录里最接近的命令,一键回车执行。长时间不用某些命令时,这个搜索功能几乎是我在终端里最有安全感的存在。
6.3 自定义 shell 函数:把复杂操作固化下来
比 alias 再进一步的是自定义函数。比如我有一个用于查看端口占用情况的习惯:先netstat -tlnp看到哪个端口被占,再根据 PID 去ps -ef里找进程。
这个操作其实可以被封装成一个函数:
find_by_port() { port=$1 netstat -tlnp 2>/dev/null | grep ":$port" pid=$(lsof -t -i :$port 2>/dev/null) echo "PID: $pid" ps -p $pid -o pid,cmd --no-headers 2>/dev/null }把这个函数写进~/.bashrc,自定义函数和 alias 一样,保存后用source ~/.bashrc生效。以后输入find_by_port 8080,终端就会把占用 8080 端口的进程和 PID 完整列出来。
这个函数本身并不复杂,但它代表了一种工作方式:把重复三遍以上的操作,固化成自己的工具。长此以往,你的命令行使用体验会从“每次输入一串陌生命令”变成“像在操作自己的贴身工具”,又稳又快。
6.4 通配符和 Tab 补全:别当打字员
还有一个经常被忽视的效率点:表格补全。Bash 里按下 Tab 能自动补全命令名、路径、文件名。很多人总觉得这个太基础了没必要练,但实际上,在cd /data/app/releases/下面有六个版本号目录,想进入某历史目录,只需要输入cd /data/app/releases/2025然后按下 Tab,剩下的部分 shell 自动补全,根本不用一个个字符去敲。
通配符同样高效。清理所有.tmp文件:
rm -f /tmp/xx_*.tmp但通配符也有风险——如果目录下没有匹配项,shell 会直接把xx_*.tmp原样传给命令,导致操作落到一个不存在的路径上。所以凡是涉及删除的通配符命令,我都建议先执行一遍:
ls /tmp/xx_*.tmp看到真实列出的文件再执行上一条删除命令。命名的课程果然很值钱,值得养成习惯。
写在最后的一些体会
聊了这么多,其实最想说的一句话是:linux 命令不是“背”出来的,也不是“炫”出来的,它是解决问题的工具箱。不同的环境、不同的故障、不同的日志格式,永远有更适合的组合方式。今天分享的排查链路和习惯沉淀,都是我在实际工作中反复锤打后留下来的套路,希望能给你带来一些触发。下次再遇到磁盘告警、服务器变慢、日志分析困难的时候,不妨先把文章里的这套思路在脑子里过一遍,大概率会让你的排查过程顺畅很多。