学 Linux 命令这事,最怕的不是不会,是学了一堆之后发现日常根本用不上。之前两篇我们过了一遍文件操作、目录切换、权限管理这类最基础的东西,说白了那叫"认识路"。这篇是 Linux 基本指令的第三篇,聊的内容才是正经八百天天要敲的:文本处理、进程管理、打包压缩、查找文件。学完这一篇,你再回头看技术群里贴的那些"看起来很高深"的命令行操作,基本不会一头雾水了。
这篇适合谁?把基础指令过完、想跨过新手门槛的人,或者 ls、cd 用得挺熟但没系统学过 grep、sed、awk 的开发者。每一条命令我都会讲清楚"它到底解决什么问题""为什么要这么写",同时带一个可以直接照着敲的真实场景案例。环境很简单,任意一台 Linux 发行版机器都行,虚拟机、云服务器或者 WSL 都无所谓。
1. 文本处理三板斧:grep、sed、awk
1.1 grep:从日志里捞东西的第一选择
grep 的全称是 global regular expression print,做的事情一句话概括:逐行扫描文件或输入流,把匹配规则的行挑出来。这是所有服务端排障的基础操作,没有之一。
先记最常用的几个写法:
bash复制grep "error" app.log # 在文件中找含 error 的行
grep -i "error" app.log # 忽略大小写,能匹配 ERROR、Error
grep -n "error" app.log # 显示行号,方便定位
grep -c "error" app.log # 只输出匹配行数
grep -v "debug" app.log # 反向匹配,把含 debug 的行过滤掉
grep -r "TODO" src/ # 递归搜索整个目录
grep -w "error" app.log # 精确匹配整个单词
为什么它是排障首选?因为日志本质上是半结构化文本,最关键的操作就是"按关键字筛"。服务突然报错,第一反应肯定是 tail -f app.log 看实时日志,然后立刻 grep "Exception" app.log 把所有异常捞出来,再按时间范围 grep "2025-01-15 10:" 切出事故窗口。这一套组合拳下来,大部分问题都能定位到具体时间点和具体代码段。
再配合管道,grep 还能直接过滤其他命令的输出。比如查进程、查端口占用、查环境变量,都是同一种套路:
bash复制ps aux | grep nginx # 查看 nginx 相关进程
env | grep PATH # 查看 PATH 环境变量
这里有两个新手的经典坑。第一,grep 匹配的是"行内包含",不是"完全等于"。搜 test 会把 testing、latest 全部带出来,需要精确匹配就得加 -w。第二,正则里的特殊字符在 grep 里是有含义的,最典型的是点号 .。你想搜 IP 地址 192.168.1.1,不加处理的话,实际上匹配的是"192 任意字符 168 任意字符 1 任意字符 1",会把 19216811 这种奇怪字符串也捞出来。搜纯文本建议用 grep -F 固定字符串模式,或者把特殊字符转义掉。
1.2 sed:不改文件也能"改"文件
sed 全称 stream editor,流式编辑器。它和 vim 最大的区别是:vim 需要人工交互,sed 是非交互、可批量、可脚本化的。适合做替换、删除、按行抽取这类操作。
bash复制sed 's/old/new/' file # 每行第一个 old 替换为 new
sed 's/old/new/g' file # 全局替换,g 就是 global
sed '3d' file # 删除第 3 行
sed '10,20d' file # 删除第 10 到 20 行
sed -n '5,15p' file # 只打印第 5 到 15 行
sed -i 's/old/new/g' config.txt # 直接修改原文件
句法里那个 s 是 substitute(替换),后面紧跟三部分组成:要查找的内容、要替换成的内容、以及 g 标记。sed 默认不会改原文件,只是把处理结果输出到终端,想看效果直接跑就行,确认没问题再加 -i 真正落地修改。这个"先预览、后落盘"的习惯极其重要,我见过不止一个人因为漏了 g,只替换了每行的第一个匹配,结果同一行里剩下的旧值全留下来了。
举一个实际场景:批量把项目代码里的开发环境接口地址换成测试环境地址。
bash复制sed -i 's#http://192.168.1.1:8080#https://api.test.example.com#g' *.js
注意这里我用了 # 作为分隔符而不是 /。因为 URL 本身包含大量斜杠,如果还用 / 当分隔符,整个命令会变成一堆转义字符,读起来像天书。sed 的分隔符理论上可以用任意字符替代,挑一个内容里不太可能出现的字符,可读性和正确性都能兼顾。这也是老手写 sed 的常见习惯。
还有一点要记住:Linux 的 GNU sed 和 macOS 自带的 BSD sed 在 -i 参数上写法不一样。Linux 直接 sed -i 就行,macOS 上写 sed -i '' 才能正常执行。跨平台写脚本的时候,这个差异经常让人莫名奇妙地报错。
1.3 awk:按列拆数据,做简单统计
awk 比 sed 更进一层,它的核心模型是:把每一行按分隔符拆成若干字段,然后对字段做操作。默认分隔符是连续的空白字符(空格或 Tab),也可以主动指定。
bash复制awk '{print $1}' file # 打印每行第一列
awk '{print $1, $NF}' file # 打印第一列和最后一列
awk -F: '{print $1}' /etc/passwd # 以冒号分隔,抽用户名字段
awk '$3 > 60 {print $1}' scores.txt # 第三列大于 60 才打印第一列
awk '{sum += $1} END {print sum}' nums.txt # 对第一列求和
awk 里有几个内建变量,理解了它们基本就入门了:$0 代表整行,$1、$2 依次是第一列、第二列;NF 是当前行的字段总数,所以 $NF 就是最后一列;NR 是当前处理的行号;END 是处理完所有行之后要执行的代码块。-F 则用于指定自定义分隔符,比如处理 /etc/passwd 这种冒号分隔的文件。
场景一,分析 Nginx 访问日志里访问量最大的 IP:
bash复制awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
这一条连招的含义是:先用 awk 把每行第一个字段(通常是客户端 IP)抽出来,sort 排序让相同 IP 排到一起,uniq -c 去重并统计出现次数,再 sort -rn 按次数从大到小排,最后 head -20 取前 20 条。这是从日志里找"谁在刷你的接口"的经典写法,几乎所有后端排查工作流里都会出现。
场景二,日志里有一列是接口耗时,想算平均响应时间:
bash复制awk '{sum += $10; count++} END {print "avg:", sum/count}' api.log
awk 内部其实支持类似 C 语言的条件、循环、数组,能做的统计比这复杂得多,但对"基本指令"这个系列来说,掌握 $N、NF、END、-F 已经足以覆盖日常工作里七八成的需求。用到的时候再翻文档,完全来得及。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 进程管理与系统状态查看
2.1 ps:给进程拍一张快照
ps 用来查看当前系统里的进程信息,它的特点是:只显示执行那一刻的状态,不会动态刷新。最常用的两种写法是:
bash复制ps aux # 以用户视角显示所有进程
ps -ef # 标准格式显示所有进程
ps aux 的输出列含义需要注意几个:PID 是进程号,%CPU 和 %MEM 是占用率,STAT 是进程状态,COMMAND 是执行的完整命令。排查"服务为什么起不来""端口为什么被占用"这类问题,第一步永远是 ps aux | grep 关键字 找到对应进程。
STAT 状态列常见的取值含义:
| 状态 | 含义 |
|---|---|
| R | running,正在运行或可运行 |
| S | sleep,可中断睡眠,等待事件 |
| D | 不可中断睡眠,通常卡在磁盘 IO |
| Z | zombie,僵尸进程,子进程结束但父进程未回收 |
| T | stopped,被暂停 |
有一个细节绝大多数新手都会踩:执行 ps aux | grep nginx 时,输出里经常会多出一行 grep --color=auto nginx,这就是 grep 自己匹配到了自己。命令本身不复杂,但第一次看到时确实会困惑。规避方法是在 grep 参数里用方括号技巧:ps aux | grep '[n]ginx',方括号让这个字符串不可能匹配到 grep 的完整命令行,结果干净很多。
2.2 top:实时看系统负载
top 是动态刷新的进程列表,按 q 退出。进入界面之后,几个快捷键必须知道:按 P 按 CPU 使用率排序,按 M 按内存使用率排序,按 k 输入 PID 直接给进程发信号。刚登录一台表现异常的服务器,我通常先敲 top,然后按 P 看看哪个进程在烧 CPU。
第一行 load average 后面跟着三个数字,分别代表过去 1 分钟、5 分钟、15 分钟的平均负载。这个地方最容易误解,负载高低要结合 CPU 核心数来看,单核机器负载 2.0 就已经排长队了,16 核机器负载 8.0 可能还空着一半核。判断标准不是"数字大不大",而是"持续是否高于核数"。
如果看到 STAT 是 Z 的僵尸进程,先别急着慌。僵尸进程本身不消耗 CPU 和内存,它只是父进程忘了"收尸"。关键看它是不是在持续堆积,偶尔一两个属于正常现象,清理方式一般是找到父进程并处理它,或者干脆重启上层服务。
2.3 kill:核心是发信号,不只是"杀掉"
很多人以为 kill 就是"把进程弄死",这个理解窄了。kill 的本质是向进程发送一个信号,进程收到信号后怎么处理,取决于信号类型和进程自身的逻辑。
bash复制kill PID # 默认发 SIGTERM,编号15,进程可自行清理善后
kill -9 PID # 发 SIGKILL,编号9,内核强制终止,无任何机会清理
kill -l # 列出所有支持的信号
两条铁律:能用 kill PID 就别一上来就用 kill -9。因为进程接收到 SIGTERM 后,还有机会去写日志、关闭连接、释放锁、保存状态,这些收尾逻辑对很多服务是保命符。直接发 SIGKILL 相当于强行拔电源,数据库可能留下半截事务,配置文件可能写到一半就断了。我自己的习惯是:先给 5 到 10 秒,看到进程没反应再升级到 -9。
按进程名处理也是常用操作:
bash复制pkill -f "python app.py" # 按完整命令行匹配
killall nginx # 按进程名全部终止
注意 pkill 的 -f 表示匹配完整命令行,不加则只匹配进程名。两条命令看似功能重叠,实际场景里 pkill -f 的匹配范围更精准,适合进程名太通用、避免误杀的情况。
2.4 系统体检四件套:df、du、free、uname
这四个命令回答四个基础问题:磁盘还有多少、哪里占了空间、内存够不够、系统是什么版本。
bash复制df -h # 查看各分区磁盘使用,-h 转成易读单位
du -sh * # 查看当前目录下各子目录的占用大小
free -h # 查看内存和交换分区
uname -a # 查看内核版本、系统架构等
du -sh * 是排查"磁盘被什么塞满了"的第一刀。进到最占空间的目录,一层层执行 du -sh */,总能逮到那个偷偷膨胀的日志目录或者缓存目录。但有一个隐藏点:du -sh * 默认不统计隐藏文件,所以当 df 显示用了 80G,而 du 算来算去只有 20G 时,要么是 .cache 这类隐藏目录作怪,要么是"文件已删除但进程仍持有句柄"导致的磁盘空间不释放。后者的典型特征是用 rm 删了文件,空间却没回来,这时候要用 lsof | grep deleted 找到对应的进程,重启它或让它重新打开文件才能释放空间。
free -h 的输出里 buff/cache 往往占了一大块,新手容易被吓到,以为内存已经耗尽。但其实这部分是内核为加速文件读写而占用的缓存,系统需要时会自动回收。真正要关注的是 available 那一列,它才是"当前还能分给新程序的内存"。
2.5 后台任务:nohup、jobs、bg/fg
这个话题在入门阶段经常被跳过,但实际部署服务时一定会遇到:关掉终端,服务就跟着退了,怎么办?
bash复制nohup python app.py > app.log 2>&1 &
这条命令拆开看:nohup 让进程忽略挂断信号,终端关了它也不退出;> app.log 把标准输出重定向到文件;2>&1 把错误输出也合并到同一个文件;最后的 & 让命令在后台运行。这个组合是"让服务脱离终端独立运行"的最基础姿势。
已经在前台跑着的任务,可以用 Ctrl+Z 挂起,然后 bg 把它放到后台继续跑,或者用 jobs 查看自当前终端启动的后台任务列表,fg 把某个后台任务重新调回前台。这些命令配合使用,开发调试阶段特别顺手。
3. 打包压缩与下载:tar、gzip、wget、curl
3.1 tar:参数顺序是有讲究的
tar 最早是磁带归档工具,现在的核心用途是打包和压缩。最常见的就是这两组命令:
bash复制tar -czvf archive.tar.gz /path/to/folder # 打包并 gzip 压缩
tar -xzvf archive.tar.gz -C /target # 解压到指定目录
tar -tzf archive.tar.gz # 列出压缩包内容,不真正解压
参数拆开看:c 创建归档,x 解压,t 列表;z 表示用 gzip 压缩或解压;v 是 verbose,显示过程;f 指定归档文件名。
这里有一条极其容易翻车的规则:f 必须放在参数序列的最后一个,因为它后面紧跟的就是文件名。写成 tar -cfz 或者 tar -zcf,命令会直接把 f 后面的字符当文件名处理,结果一脸懵。如果你只想打包不想压缩,那就去掉 z,用 tar -cvf archive.tar,对应文件后缀是 .tar。而 .tar.gz 是"先打包、再 gzip 压缩"双重动作的结果,这个格式跨平台兼容性最好,服务器间传文件基本都用它。
解压时习惯性地写 -C 指定目标目录,能避免压缩包内部结构混乱时炸出一堆文件到当前目录。解压之前先用 tar -tzf 看看包内是否有一个统一的顶层目录,是就直接解压,不是就先建一个目录再解压进去。这个习惯在接手别人的压缩包时能省下大量整理时间。
压缩率方面有个常识性排序:gzip 速度最快、文件较大,bzip2 中庸,xz 压缩率最高但慢。日常使用 .tar.gz 已经足够,只有冷备份归档才值得用 xz 去进一步压缩。
3.2 zip/unzip:跨平台场景的稳妥选择
zip 的好处是 Windows、macOS、Linux 三方通吃,Linux 也内置了解压 Windows 传来的 zip 文件的能力。
bash复制zip -r project.zip ./project # -r 递归整个目录
unzip project.zip # 解压到当前目录
unzip -l project.zip # 列出压缩包内容
unzip -o project.zip -d /target # 覆盖解压到指定目录
给同事发压缩包的时候,只要不确定对方在什么操作系统上,我一般直接发 zip,而不是 tar.gz。少一轮"传过去解不开"的沟通成本,这比压缩率重要得多。
3.3 wget 和 curl:一个下载,一个调试
wget 和 curl 都能从网络拉取资源,但定位不同。wget 更偏下载,支持断点续传和递归抓取;curl 更偏接口交互,日常调试 HTTP 接口的场景更多。
bash复制wget -c https://example.com/file.tar.gz # -c 断点续传
curl -O https://example.com/file.tar.gz # -O 保存成原文件名
curl -i http://localhost:8080/api/health # 查看响应头
curl -X POST -d '{"key":"value"}' http://localhost:8080/api
真实场景里,wget 适合下载大文件,网络中断了重新执行还能续传;curl 适合验证本机服务是否正常,比如 curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health 一行拿到 HTTP 状态码,做健康检查脚本相当好用。
4. 查找文件:find 与 locate
4.1 find:慢,但万能
find 是真正遍历文件系统的搜索器,参数非常丰富,代价是搜索大目录时速度慢。
bash复制find . -name "*.log" # 按文件名匹配
find /var/log -name "*.log" -mtime +7 # 找出修改时间在 7 天前的文件
find / -size +100M # 找出大于 100M 的文件
find . -type d -name "node_modules" # 按类型找目录
find . -name "*.tmp" -exec rm {} \; # 找到后交给 rm 删除
新手对 find 的第一个困惑是:路径参数必须写。find -name "*.log" 直接写会报错,正确写法是 find . -name "*.log",默认是当前目录,但必须显式用 . 表示。-mtime 按天过滤,-mmin 按分钟过滤,这两个参数在排查"某个时间点前后到底改了什么文件"时特别有用。
-exec 是对每一个搜索结果执行后面的命令,结尾的 \; 表示命令结束,漏掉分号会直接报语法错误。嫌 -exec 啰嗦的人常用 xargs 替代,但 -exec 在处理含特殊字符的文件名时更稳妥,而且不容易触发命令行长度上限。
大范围搜索时,强烈建议加 -maxdepth 限制层级。比如 find / -name "xxx.conf" -maxdepth 3,全盘找一遍可能要几分钟,限制深度后效果立竿见影。实际情况中九成需求的答案就在前几层目录里。
4.2 locate:秒出的前提是数据库
locate 的机制和 find 完全不同,它不是实时遍历,而是查询系统预建的数据库索引,所以快。
bash复制locate nginx.conf
sudo updatedb # 手动更新数据库
代价是数据库不是实时更新的,刚创建的文件大概率查不到。需要可靠结果就先 updatedb。整体来说现在发行版里 locate 的出场率在下降,主力还是 find,这一节了解即可。
5. 软链接与硬链接:ln 没那么玄
5.1 软链接:快捷方式与版本切换
软链接(符号链接)可以类比成 Windows 的快捷方式,它本身只是个指向目标路径的小文件。
bash复制ln -s /opt/node/v16/bin/node /usr/local/bin/node
第二个参数是链接名,第一个参数是目标路径。日常用途主要有两类:一是把某些工具的可执行文件放到 PATH 目录下;二是做版本切换,比如服务器上同时装了多套版本,/usr/local/bin/tool 是一个软链接,指向当前默认版本,切换就一条命令:
bash复制ln -sfn /opt/tool-v2/bin/tool /usr/local/bin/tool
-s 是软链接,-f 是强制覆盖已存在的链接,-n 防止把已有链接当目录处理。软链接的弱点在于它保存的是路径,一旦原目标被删掉或移走,链接就失效了,ls -l 输出里目标路径标红且报 No such file。排查失效链接,第一步就是用 ls -l 看链接指向哪里、目标是否还在。
5.2 硬链接:同一个文件的多重身份
硬链接和软链接完全不同,它不单独存路径,而是让多个目录项指向同一个 inode。也就是说,硬链接其实就是在另一个位置给同一个文件内容起了一个新名字。
bash复制ln /data/file.txt /data/link.txt
创建之后,两个名字指向同一份数据,任何一边改动,另一边同步可见。即使删掉原文件,硬链接仍然能正常读内容,因为它和 inode 直接关联,不依赖文件路径。
不过硬链接有一些限制:不能跨文件系统创建,不支持链接目录,使用场景远不如软链接频繁。但理解它对理解 Linux 的文件系统结构很有帮助,面试概念题里也经常考。一句话区分两者:软链接存"通往目标的路径",目标是空的就失效;硬链接直接和数据的 inode 挂钩,路径修改与否都不影响。
6. 日常排障与我的常用"连招"
6.1 日志问题排查的标准动作
服务挂掉、接口变慢这类事故,我通常按下面的顺序来:
bash复制tail -f app.log # 先看最新的日志流
grep "ERROR" app.log | tail -50 # 把最近错误筛出来
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
这一套下来,突发故障基本都能锁定到大致范围。见过不少同事一遇到线上问题就打开编辑器一行一行翻日志,其实远不如先用 grep 缩小范围、用 awk 做统计来得快。日志本身是结构化数据,先让命令帮你干活,再人肉看细节,效率完全不同。
6.2 磁盘被写满的排查路径
磁盘告警是最常见的事故之一,按顺序走基本不会乱:
bash复制df -h # 第一步:确认哪个分区满了
cd / && du -sh * 2>/dev/null # 第二步:看根目录下谁最占空间
find / -size +1G 2>/dev/null | head # 第三步:直接找大文件
定位到目标之后,该归档归档,该清理清理。如果删了文件空间依然没回来,原因多半在前面提过的"有进程仍持有已删除文件的句柄",用 lsof | grep deleted 找到进程处理。这条路径我走了很多次,每一次都有效。
6.3 几条保命经验
- 能不用的
sudo就不用,最小权限原则不只是安全要求,更是防止自己手滑。尤其是sudo rm -rf这种组合,是无数线上事故的根源,能避免就尽量避免。 - 任何带破坏性的命令(
rm、mv、sed -i)第一次执行之前,先跑一个不带破坏性的版本预览结果,确认和预期一致再落地。 - 养成敲 Tab 补全的习惯,少打几个字是小事,减少路径拼错是大事。配合
history加 Ctrl+R 反向搜索,高频命令的复用成本会大幅降低。 - 练手不用怕。在自己虚拟机或容器里随便折腾,把文件删错、把服务搞挂,都没关系,多踩几次坑之后,记忆反而比背书深得多。
我现在的习惯是,遇到一个新命令,先 man 看一眼参数,再在自己环境里试两遍,最后封装成自己的常用别名。不管你是刚入门还是已经在用了,把这篇里的命令串起来、配合实际日志过几遍,一定会明显感觉到对终端的掌控力上了一个台阶。Linux 这玩意儿没有捷径,但绝对有复利,每多会一条命令,后面的路就顺一分。
