ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux命令实用指南:从文件操作到网络排查的深度解析

Linux命令实用指南:从文件操作到网络排查的深度解析

很多人用了好几年 Linux,命令也敲得飞快,但真到排障的时候还是会手忙脚乱。这篇文章不打算罗列命令手册,而是想把日常最常用的一批 Linux 命令重新讲透:它们为什么这么设计,使用中真正容易踩的坑在哪,怎么组合起来解决实际问题。适合刚入门的开发者建立正确认知,也适合已经写了不少脚本的老手回来查漏补缺。

命令这个东西,单独背参数没有意义,关键是要理解每条命令在链路里的位置。文件操作管的是数据怎么放,文本处理管的是内容怎么筛,进程管理管的是服务怎么跑,权限体系管的是谁能碰,网络工具管的是数据怎么通。把这五块串起来,你面对一台陌生服务器时就不会慌。

1. 文件与目录操作:路径、通配符与链接的底层逻辑

1.1 绝对路径与相对路径不只是写法差异

很多教程只说绝对路径从/开始,相对路径从当前目录开始,但很少说清楚这个差异在真实环境里会造成什么后果。我的建议是:交互式操作时相对路径很方便,但凡是写进脚本、定时任务、systemd 服务里的路径,一律用绝对路径,或者先cd到固定目录再执行。原因很简单:脚本的执行目录不是固定的。

手动在终端里跑没问题,但定时任务默认可能从用户的家目录或者根目录开始执行。如果你脚本里写的是rm -rf ./cache,那清理的可能不是你想清理的目录。我见过某开发者为了省事在脚本里连续cd,结果最后一段命令运行在了错误的目录,导致文件被删错位置。这个坑踩一次就长记性了。

另外一个实用细节:cd -能回到上一次所在的目录,原理是 shell 把上一个目录记录在环境变量$OLDPWD里。在脚本里如果想在两个目录之间来回切换,可以这样:

cd /data/app && ./build.sh && cd -

不过更稳的做法还是用子 shell 或者pushd、popd,避免脚本中途切换目录把后续逻辑搞乱。子 shell 的写法是:

(cd /data/app && ./build.sh)

这样目录切换只影响子 shell 内部,父 shell 的当前目录不变。这个细节在写部署脚本时特别实用。

1.2 通配符与花括号展开

通配符*、?、[]是新手最先接触的批量匹配方式。bash 会先做路径展开,再把展开后的结果交给命令。比如ls *.log会把所有以.log结尾的文件名列出来,没有匹配时 bash 会把原来的*.log字符串原样传给命令,这时候ls会提示文件不存在。

这里有个非常危险的场景:如果目录里没有匹配项,执行rm *.txt会报 "No such file or directory",这还算好的。但如果因为变量为空导致rm -rf $DIR/*被展开成rm -rf /,后果就是灾难性的。所以脚本里如果变量可能为空,必须加判断:

if [ -n "$DIR" ]; then rm -rf "$DIR"/* fi

还有个很容易被忽略的技能:花括号展开。它和通配符不一样,不依赖文件是否存在。比如touch {a,b,c}.txt会创建三个文件,for i in {1..10}; do echo $i; done可以生成序列。批量备份时可以用:

cp app.log{,.bak}

这一条会把app.log复制成app.log.bak,比打两遍路径方便得多。如果要对一批文件做相同操作,花括号展开比*更可控,因为它不会误匹配不该处理的文件。

1.3 硬链接与软链接的取舍

ln默认创建硬链接,ln -s创建软链接。很多人只知道"软链接像快捷方式",但没搞懂硬链接的本质。硬链接是同一个 inode 的多个目录项,删除任何一个路径,数据都还在;软链接是保存目标路径的独立文件,目标被删除后软链接就变成了断链。

什么时候用硬链接?典型场景是日志轮转和备份。比如某些日志管理工具会把当前日志文件硬链接到另一个地方,这样即使原路径被轮转删除,备份路径里的数据仍然存在。但硬链接有两个限制:不能跨文件系统,不能对目录创建。想给目录做链接就只能用软链接。

软链接最坑的一点是相对路径的写法。如果你在/data/app下面执行ln -s /data/app/config.ini /tmp/config.ini没问题,但如果你用相对路径写ln -s config.ini /tmp/config.ini,这个链接保存的是相对于/tmp的位置,跳转到/tmp后再访问config.ini就会断裂。经验是:创建软链接时,尽量用绝对路径,或者在目标目录下算好相对路径再写。

2. 文本处理三剑客:grep、sed、awk的正确组合方式

2.1 grep:比你想的更能"过滤"

grep是很多人学会的第一个过滤命令,但使用水平普遍停留在grep keyword file。其实配好参数能解决很多实际问题:

  • -E使用扩展正则,省掉一堆反斜杠。
  • -v反向匹配。
  • -c只计数。
  • -o只输出匹配到的部分而不是整行。
  • -q静默模式,不输出任何内容,只返回退出码。

退出码在脚本里特别好用。grep的退出码是:0 表示找到匹配,1 表示没有匹配,2 表示文件不存在或读取失败。所以可以这样写:

if grep -q "ERROR" server.log; then echo "日志里有异常" fi

如果直接写grep "ERROR" server.log,匹配结果会刷屏,在脚本里没有意义。静默模式就是要告诉 shell:我只关心有没有,不关心具体内容。

另一个容易踩的坑是二进制文件。grep遇到二进制文件会输出Binary file xxx matches,这个提示有时候会干扰管道解析。可以加-a把二进制当文本处理,或者用--include='*.log'限制只搜索日志文件。比如统计当前代码库里 Python 文件的待办数量:

grep -rn --include='*.py' "TODO" . | wc -l

一条命令解决人工翻文件的低效问题。

2.2 sed:地址定界与批量替换

sed是流编辑器,最常见的用法是s/old/new/g做全局替换。但真正的威力在于地址定界。比如:

sed -n '10,20p' file.txt # 只看第10到20行 sed '/^#/d' config.conf # 删除注释行 sed '2,5s/^/prefix_/' file.txt # 给第2到5行加前缀

批量替换是最高频的操作,但有一个非常常见的误区:不加-i的时候,sed只是把处理结果输出到屏幕,原文件纹丝不动。很多人第一次用sed 's/a/b/g' file,看完屏幕以为自己改好了,结果文件根本没变。要真正写回文件必须加-i参数。

-i的语法在不同系统上还不一样。Linux 上的 GNU sed 可以直接写sed -i 's/a/b/g' file,而 mac 自带的 BSD sed 要求写成sed -i '' 's/a/b/g' file。如果我写的脚本要跨平台使用,我通常用perl -pi -e 's/a/b/g' file替代,语法统一,不容易踩系统差异的坑。

sed替换里的&表示被匹配到的完整内容,这个技巧用于给匹配内容加装饰很顺手。比如把配置文件里所有端口号行加上注释:

sed -i 's/^port .*/# &/' config.conf

原来port 8080会变成# port 8080,不用手动补注释符。

2.3 awk:列处理与状态机的思想

awk是文本处理里最难学但最值得学的工具。很多人被它复杂的语法劝退,其实只要抓住一点:awk把每一行拆成列,然后对每一列做处理。默认按空白分隔,$1是第一列,$NF是最后一列,$0是整行,NR是当前行号,NF是列数。

最实用的场景是配合命令输出做统计。比如提取根分区磁盘使用率:

df -h / | awk 'NR==2 {print $5}'

NR==2是因为df输出第一行是表头,取第二行才是根分区信息。如果想算某列最大值:

awk '{if($3>max) max=$3} END{print max}' data.txt

BEGIN和END分别在第一行处理之前和最后一行处理之后执行,这种模式很适合写简单报表。很多人以为awk只能在文本文件上工作,其实它可以配合任意命令的输出使用,这才是它真正的价值。

有一个细节非常容易踩坑:在awk的 program 里直接使用 shell 变量会失效,因为单引号里的内容不会做 shell 展开。正确做法是用-v传参:

threshold=80 df -h / | awk -v t="$threshold" 'NR==2 {gsub("%","",$5); if($5+0>t) print $5}'

gsub在这里把百分号去掉,+0把字符串转成数字,才能和阈值比较。这个写法可能有点长,但实际排查磁盘告警时非常管用。

3. 进程与资源问题:从定位到处理的完整排查思路

3.1 ps 输出的字段怎么看

服务器负载高了,第一个念头就是跑ps aux。这一堆列分别代表:USER运行用户,PID进程号,%CPU和%MEM,VSZ虚拟内存,RSS常驻物理内存,TTY关联终端,STAT进程状态,TIME累计 CPU 时间,COMMAND是具体命令。

STAT这一列是判断进程健康度的关键。R表示正在运行,S表示可中断睡眠,D表示不可中断睡眠,通常是进程正在等待磁盘或网络 IO,Z表示僵尸进程。如果看到一堆Z,说明父进程没有正确回收子进程,这通常需要重启父进程才能清掉。

排障时最常用的是按资源排序。快速找出 CPU 占用最高的前十个进程:

ps aux --sort=-%cpu | head -10

如果要查内存,就把%cpu换成%mem。我个人的习惯是,看到服务器告警先跑这一条,再配合top看实时变化,判断是持续占用还是瞬时抖动。

还有个细节:pgrep和pidof可以快速找到进程号,不必在ps输出里翻。比如pgrep -f "nginx: worker"会精确匹配完整命令行,比只匹配进程名更靠谱,因为进程名可能被截断。

3.2 top/htop 里真正需要盯的指标

top第一行的load average有三个数字,分别代表 1 分钟、5 分钟、15 分钟的平均负载。很多人以为负载高就是 CPU 忙,不完全是。负载统计的是处于运行状态和不可中断状态的进程数量。如果服务器是 8 核,负载持续超过 8,通常说明资源已经饱和,或者有进程卡在 IO 上。

%Cpu(s)这一行里,us是用户态 CPU,sy是系统态 CPU,id是空闲,wa是 IO 等待。最有迷惑性的是wa。当wa很高而id很低时,说明 CPU 在等磁盘,这时候再怎么优化代码都是白费力气,应该去查存储延迟、磁盘队列和网络文件系统的情况。

内存部分,很多人一看free -h里面used很高就紧张。其实buff/cache是内核用空闲内存做的缓存,是需要时可以被回收的,并不是泄漏。真正要看的是available这一列,它表示在不交换的情况下还能分配给新进程的内存,这才是实际可用量的估算值。

top里还可以直接按P键按 CPU 排序,按M键按内存排序,这在交互式排查时比反复敲命令快得多。htop是top的增强版,支持彩色显示和鼠标操作,个人用起来体验更好,但多数服务器默认没有装,需要手动安装。

3.3 处理僵死进程与退出码

服务挂了的处理顺序,很多人一上来就kill -9,这不推荐。kill默认发送TERM信号,相当于礼貌地请进程退出,进程可以捕获TERM做清理工作。如果进程已经卡死,TERM没有效果,再考虑KILL。KILL信号不能被捕获,由内核强制结束,但它不保证进程的清理逻辑被执行,所以是最后手段。

kill -9都杀不掉的时候,先看进程状态是不是D。D状态表示进程处于不可中断的内核态等待,通常是磁盘 IO 或网络存储卡住,信号要等 IO 返回后才能处理。这种时候你只能排查底层存储,单纯反复kill没有意义。

另外不要忘了timeout命令。执行一条可能卡住的命令时,用timeout 30 ./some_task.sh可以让它在 30 秒后自动超时退出。这在写脚本时非常重要,能避免整个任务因为一条命令挂死而无法结束。

后台运行也需要区分。命令加&只是放到后台,终端关闭时它可能收到挂断信号退出。nohup command &才能让命令在退出终端后继续运行。但这两个都不是真正的守护化,如果需要长期跑服务,应该用 systemd 管理,它负责进程拉起、重启、日志收集,比裸nohup可靠得多。

4. 权限不只是chmod:用户、组与umask的协作机制

4.1 把权限位当成二进制来读

权限位其实就三组:属主、属组、其他人,每组三位rwx。数字表示法里r=4, w=2, x=1,所以744就是属主可读写执行,其他人只读。

目录的权限和文件的权限有本质区别。目录的r权限只能让你列出文件名,w权限能让你在目录里新建或删除文件,x权限才是进入目录的通行证。很多人遇到Permission denied时只想到chmod 644或755,其实如果是要进入目录访问里面的文件,必须有x权限。推荐的做法是:目录统一用755,普通文件用644,只有明确需要执行的脚本才用755。

有个经典场景最能说明目录x权限的作用:如果一个目录只有r--权限,你能ls看到文件名,但cd进入时会被拒绝,访问里面任何一个文件也都提示无权限。这个表现很容易让人误以为是文件权限的问题,实际是目录缺x。

4.2 为什么 777 是坏味道

网上很多教程为了省事直接chmod -R 777,这种做法的危害只有在出问题时才凸显。777意味着任何人都能读写执行,一旦某个目录被写入恶意脚本,服务权限边界就完全消失了。最小权限原则不是空话。

如果只是需要让服务能写某个目录,更合理的做法是确定服务运行的用户和组,然后把目录属主设为该账号,或者把目录属组设为服务账号所在组,再给属组加写权限。例如:

chown app_user:app_group /var/www/uploads chmod 750 /var/www/uploads

这样只有服务账号能写,其他人连进入目录都不行。权限往小里给,后面排查问题时能少很多安全顾虑。

还有一个容易被忽略的点:粘滞位。/tmp目录的权限通常显示为1777,那个前置1就是粘滞位。它表示在这个共享目录下,只有文件属主、目录属主或管理员能删除自己的文件。如果没有粘滞位,任何能进入/tmp的人都能删掉别人的临时文件。共享目录加粘滞位是常见做法:

chmod +t /shared/tmp

4.3 umask 决定新文件的默认权限

umask是一条很少被注意、但非常影响协作的命令。它是权限屏蔽字,不是权限本身。通常发行版默认umask 022,意思是新创建的文件需要去掉属组和其他人的写权限。所以新建普通文件是644,新目录是755。

为什么文件和目录初始权限不一样?因为文件创建时的默认权限是666,目录是777,再减去umask。文件从没有执行权限的666减去022,得到644;目录从777减去022,得到755。这个机制保证了普通新建文件默认不是可执行的。

如果团队成员经常发现上传的文件别人无法读取或修改,先查umask。在/etc/profile或~/.bashrc里可以设置。比如设置umask 002,那么同组的人可以写新文件,适合协作目录。但要权衡安全性,公共环境不要轻易改成000,那会让所有人都能改你的文件。

4.4 chown与属主属组导致的启动失败

线上服务启不来,第一件事看日志,第二件事看文件属主。很多手动部署场景里,用管理员账号拷贝文件到服务目录,结果服务以专用账号运行,读取不到属主是自己的文件,直接报权限错误。解决方式很简单:ls -l看属主,然后:

chown -R app_user:app_group /data/service

但chown -R也是一个危险操作,它会递归修改大规模目录。如果路径写错,可能导致系统文件属主混乱。执行之前,先用ls -ld确认目标路径,再操作。改完一遍之后抽查几个文件,确认属主符合预期,别急着收工。

5. 网络排查:命令不是背出来的,是查出来的

5.1 先确认连通性:ping 的局限

ping是大家最爱用的网络测试命令,但它的局限性很多人不清楚。ping走的是 ICMP 协议,很多云主机默认禁止 ICMP 响应,所以ping不通并不代表服务不可用;反过来,ping通了也不代表应用层正常。比如数据库服务没启动,但主机ping照常通。

排查问题时,我一般分两层看:主机层面用ping确认基本连通,端口层面用nc或telnet确认端口可达。一条命令直接测端口:

nc -zv target_host 3306

返回Connected说明端口可达。这比ping靠谱得多,因为它直接测试了你真正关心的那条链路。

5.2 端口与连接:ss 比 netstat 更值得学

netstat是老牌命令,但很多新系统默认已经不自带了,推荐直接用ss。ss是 socket statistics 的缩写,速度快,信息全。最常用的一组参数是:

ss -tunlp

-t显示 TCP,-u显示 UDP,-n不做域名解析,-l只看监听端口,-p显示对应进程。这一条命令就能看出哪个端口被哪个进程占用。

如果想看当前所有已建立的连接:

ss -tn state established

排查连接数异常时很有用。TIME_WAIT状态多通常不是问题,它是 TCP 主动关闭连接的正常过渡状态。如果看到大量SYN-SENT或SYN-RECV,那才是需要警惕的,说明连接根本建立不起来,可能是对端拒绝或网络被拦截。

5.3 curl:一条命令完成接口调试

curl是排查应用层问题的最强命令,没有之一。推荐几个最常用的参数组合:

  • curl -i显示响应头。
  • curl -v打印完整请求和响应过程,包括 TLS 握手。
  • curl -L跟随重定向。
  • curl -X POST -H 'Content-Type: application/json' -d '{"name":"test"}' http://host/api发送带 JSON 的 POST 请求。
  • curl --max-time 5限制超时时间,防止命令卡住。

实际排查时,先用-i看状态码。如果是502或504,基本可以判断问题在反向代理后面的服务,而不是curl本身。如果是200但内容不对,可能是缓存或路由问题,可以在 URL 后面加个无意义参数绕过缓存。

curl -v输出里重点看这几处:Connected to表示 TCP 连接建立成功,SSL connection using TLS表示 TLS 握手正常,GET /path HTTP/1.1是实际请求行。如果 SSL 报错而业务用的是 HTTP,那就说明访问协议选错了。

5.4 DNS层面:getent与解析顺序

很多网络问题出在 DNS 解析上。ping域名通,但curl域名不通,这类现象多半和解析结果或应用自身的解析方式有关。系统解析主要看/etc/nsswitch.conf里hosts的配置顺序,一般默认是files dns,也就是先查/etc/hosts,再查 DNS 服务器。

getent hosts 域名可以显示系统最终采用的解析结果。修改/etc/hosts后大多数应用即时生效,但如果应用用了自己的 DNS 缓存或客户端,可能要重启进程才能读到新记录。

排查时也可以用dig +short 域名直接查 DNS 记录,但要记住:dig跳过/etc/hosts,它查的是 DNS 服务器,和系统实际解析不完全一致。所以判断"为什么这个域名解析到不同 IP"时,要以getent hosts的输出为准。

最后说一点我自己的感受。Linux 命令学到最后,记忆负担其实不大,真正值钱的是你理解每个命令输出里的关键字段,并把它们串成排查思路。比如看到负载高,想到ps;看到可用内存低,想到buff/cache;看到权限拒绝,想到属主、umask、目录x权限;看到端口不通,想到ss和curl。这套东西不是一天练成的,但只要每次遇到问题都把链路上每个命令跑一遍,慢慢就会形成肌肉记忆。希望这篇能帮你把常用命令用得更明白。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进