news 2026/11/6 5:47:14

Linux操作命令体系:文件、文本、进程诊断与脚本化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux操作命令体系:文件、文本、进程诊断与脚本化

1. 先把命令行当成一门"操作系统方言"来学

很多人第一次接触 Linux 操作命令,是在一台只能敲键盘、没有图形界面的机器上。屏幕上只有一个光标在闪,输入ls回车,出来一串文件名,然后就开始背:cd是切目录,mkdir是建目录,rm是删文件。背了三十条,一上真机还是懵。问题不在于命令太多,而在于没搞明白这些命令背后共用的一套语言规则。Linux 操作命令本质上是一门"方言":它的语法极其简单——命令名 + 选项 + 参数,三件套;词根也少得可怜,大量命令成簇出现(ls/lsof/lsblk,ps/pstree/pgrep),动词和名词的组合方式高度一致。你把规则摸透,看似零散的上百条命令会自己归位。

这篇东西写给两类人:一类是刚上手服务器、被Permission denied和command not found折腾得没脾气的朋友;另一类是用了几年命令,但一直是"复制粘贴型选手"、想系统补齐底层理解的同学。我不会按字母表罗列命令手册,那种内容你man一下就有;我要讲的是这些命令在真实场景里怎么组合、为什么这么组合、哪里会出事。全文会围绕文件操作、文本处理、进程与端口诊断、脚本化四个高频区展开,每个区都配可复现的操作和踩过的坑。看完你至少能做到:拿到一台陌生的机器,知道先敲什么、看什么、怎么定位问题。

1.1 一切皆文件:理解这条铁律之后命令就少背一半

Linux 操作命令之所以能收敛得这么好,根源是"一切皆文件"这个设计。普通文本是文件,目录是文件,硬件设备是文件,内核状态也是文件。带来的直接结果是:读写、查看、复制、权限这套操作可以通用。

举几个具体例子,你就能感觉到这套设计的省力之处:

  • 想看机器有几核 CPU,不用装任何工具,cat /proc/cpuinfo就行,因为 CPU 信息被暴露成了文件。
  • 想看内存用量,cat /proc/meminfo。
  • 想临时静音声卡、调节音量,操作的是/dev/snd/下的设备节点。
  • 磁盘本身是/dev/sda、/dev/nvme0n1,对它做dd就是对整块盘读写。
  • 甚至连进程都能当文件看,进程号 1234 的信息在/proc/1234/下面,cat /proc/1234/cmdline就能看到它的启动命令。

理解这一点之后,很多"专用命令"你会发现只是"通用命令 + 特定路径"的封装。lsblk输出的信息,你在/sys/block/下也能翻到;free读的就是/proc/meminfo。

具体怎么用这条规律来减少记忆负担?我常用的思路是:遇到一个不熟悉的资源,先去/proc和/sys里找对应目录,找到之后用cat、grep、awk直接处理,往往比学一个新命令还快。比如排查某个进程打开了多少文件描述符:

# 进程 1234 当前打开的文件描述符数量 ls /proc/1234/fd | wc -l # 该进程的文件描述符上限 cat /proc/1234/limits | grep "open files"

这两行不需要lsof,在极度精简的容器镜像里(很多镜像根本不带lsof)特别好使。我第一次在某个 Alpine 基础镜像里发现lsof: not found的时候,就是靠/proc把问题绕过去的。

注意:/proc和/sys里的大量文件是只读的虚拟文件,echo进去可能没反应,也可能直接改变内核行为。比如往/proc/sys/下的文件写值属于运行时调参,重启即失效,生产机上不要随手试。

1.2 命令的运行链路:PATH、别名、内建命令与外部命令

敲下ls回车之后发生了什么,这件事搞清楚,能解决掉一大半"为什么我的命令行为和教程不一样"的困惑。

大致流程是这样的:shell 拿到你输入的整行,先做分词和展开(变量替换、通配符展开、命令替换),然后判断这是不是 shell 内建命令(cd、echo、export、alias都是内建的),如果是就直接在 shell 进程内执行;如果不是,就到PATH环境变量列出的目录里从左到右找同名可执行文件,找到第一个就执行,后面的不再找。

由此衍生出几个非常实用的排查动作:

# 看一个命令到底走的是哪个路径 type -a ls # 只看可执行文件路径 which python3 # 看 PATH 的完整内容,注意顺序就是优先级 echo $PATH | tr ':' '\n'

type -a比which更值得推荐,因为它会告诉你"这是别名"、"这是内建命令"、"这是 /usr/bin 下的文件",信息更全。我遇到过最典型的一个坑:某台机器上ls被加了一个别名alias ls='ls --color=auto -F',某人写脚本时依赖ls的输出做解析,结果在交互式 shell 里跑得好好的,放进crontab就出错——因为 crontab 的 shell 不加载~/.bashrc,别名不生效,输出格式变了,解析全崩。

再说PATH顺序。默认一般是/usr/local/bin:/usr/bin:/bin这种形态,/usr/local/bin排在前面,这就是为什么自己编译安装的软件会覆盖系统自带的同名命令。如果你装了两个版本的同一工具(比如两个版本的编译环境),which出来的路径和你以为的不一致,八成就是PATH顺序问题。临时解决可以直接用绝对路径调用,长期解决就把PATH调整写进 shell 配置文件。

# 临时把某个目录提到 PATH 最前面,只对当前会话有效 export PATH=/opt/tools/bin:$PATH

补一句内建命令的坑:cd、export、alias、umask这些是内建命令,它们改变的是当前 shell 进程的状态。所以你在脚本里写cd /some/dir是有效的,但如果你写bash -c "cd /some/dir",那个cd只影响子进程,父进程的当前目录纹丝不动。这个区别在写脚本时非常关键,很多人第一次写cd之后再执行相对路径命令失败,就是这个原因。

1.3 按场景而不是按字母表来记命令

我的建议是彻底放弃"背命令清单"这件事,改成建立场景索引。真实工作里你遇到的永远是问题,不是命令。下面这张表是我自己桌面便签上的版本,按"我要干什么"来组织,比任何命令大全都好用。

我要做的事首选命令备选/补充
找文件在哪findlocate(需先建索引)
在文件内容里找字符串grep -rnrg(装了就更快)
看文件尾部新增tail -ftail -F(应对日志轮转)
看磁盘空间够不够df -hdu -sh *找大户
看谁占着 CPUtopps aux --sort=-%cpu | head
看端口被谁占了ss -lntplsof -i:端口
看进程打开的文件lsof -p PIDls /proc/PID/fd
批量改文件内容sed -iperl -pi -e(正则更强)
按列统计awkcut+sort+uniq -c
定时执行crontab -esystemd timer

表格里的"备选"列不是摆设。同一个问题在不同机器上最优解不同:容器里可能没有lsof,老系统上可能没有ss只有netstat,最小化安装可能没装locate的数据库。真正熟练的人脑子里存的是一张"问题 → 多条路径"的网,而不是一条命令。

这也解释了一个常见现象:新手看到别人敲出一长串管道命令会觉得对方"记性好"。其实不是,那串命令是现场拼出来的,每一段解决一个小问题,靠管道串起来。第 5 节我们会专门讲管道怎么拼。

2. 文件与目录操作:最容易出事的一块

文件操作是所有 Linux 操作命令里使用频率最高、同时也是破坏力最大的一块。ls敲错顶多没输出,rm敲错可能就没了。这一节我把常用命令里"真正有用的那部分参数"挑出来讲,不讲全量选项,只讲现场用得上和容易翻车的。

2.1 ls、cd、find:把"找东西"这件事做扎实

ls大家都熟,但有三个参数我认为必须形成肌肉记忆:

ls -lh # 人类可读的大小,排查磁盘占用第一眼 ls -la # 显示隐藏文件,很多配置问题就藏在点文件里 ls -lt # 按修改时间倒序,找"最近谁动过" ls -ltr # 反过来,最新的在最下面,配合长输出看更顺

ls -lt这个组合我用得极多。某次线上配置突然失效,第一步就是ls -lt /etc/某服务目录/,一眼看到某个配置文件十分钟前被改过,问题范围瞬间从"玄学"收窄到"某次变更"。

cd本身没什么好说的,但有两个小技巧值得养成习惯:

cd - # 回到上一个目录,两个目录之间来回跳 cd # 不带参数,直接回当前用户的家目录

find是文件操作里信息量最大的一个命令,也最容易被写成"抄来的一段看不懂的咒语"。我把它的结构拆开:find 起点 条件 动作。三部分各管一件事,读的时候按块读。

# 在 /var/log 下找 7 天内修改过、大于 100M 的 .log 文件 find /var/log -type f -name "*.log" -mtime -7 -size +100M # 找出所有权限是 777 的文件(安全检查常用) find /data -type f -perm 0777 # 找出 30 天前的文件并删除(删除前务必先只打印验证) find /data/tmp -type f -mtime +30 -print find /data/tmp -type f -mtime +30 -delete

这里必须强调一条铁律:任何带-delete或-exec rm的find,第一次执行时一定要把动作换成-print先看一遍结果。我见过不止一次有人把-mtime +30写成-mtime -30,符号一颠倒,删的是最近 30 天的文件,性质完全反了。-print那一步只花你三秒钟。

时间参数这块也值得单独说清楚,因为它是误删重灾区:

参数含义记忆方法
-mtime -77 天以内修改过负号像"最近"
-mtime +77 天以前修改过正号像"以前"
-mmin -3030 分钟以内m 换成分钟
-newer file.txt比 file.txt 更新的拿一个基准文件对比

-newer这个参数实际很好用。比如你怀疑某次发布之后系统状态发生了变化,拿发布时留下的一个时间戳文件当基准,find /etc -newer /tmp/deploy_stamp,就能把所有在那之后被改动的配置列出来。

2.2 rm、cp、mv 的三条保命习惯

rm的危险性不需要解释,但光说"小心点"没用,得有具体机制。我自己固定执行三条:

第一条:rm -rf之前先ls同一个路径。这不是形式主义。路径拼错是最高频的误删原因,/data/app/logs少打一个s就变成/data/app/log,可能是个完全不同的东西。先ls一遍,眼睛确认输出的是你想删的东西,再按上方向键改命令。

第二条:变量一定要加引号,且用${VAR}形式。这条在脚本里是硬性的:

# 危险写法 rm -rf $TARGET_DIR/ # 相对安全的写法 rm -rf "${TARGET_DIR:?}/"

${VAR:?}的含义是:如果变量为空或未定义,直接报错退出,不执行后面的命令。少了这个保护,一旦变量因为某种原因没赋值,rm -rf "$TARGET_DIR/"会变成rm -rf /,后果不用多说。这是 shell 里少数几个真正能"救命"的语法糖。

第三条:重要的删除操作走"移动而非删除"。在生产机上我的习惯是建一个/data/.trash/目录,删除时用mv移进去,定期清理。多占一点磁盘,换回来的是后悔药。

# 一个极简的"回收站"函数,加进 ~/.bashrc trash() { mkdir -p /data/.trash && mv -v "$@" "/data/.trash/$(date +%s)_$(basename "$1")"; }

cp和mv的坑主要集中在两点。一是覆盖行为:cp默认直接覆盖不提示,mv也一样。想有提示就加-i,或者在~/.bashrc里加alias cp='cp -i'、alias mv='mv -i'、alias rm='rm -i'。二是跨文件系统移动:同一个文件系统内mv是改目录项,瞬间完成;跨文件系统时mv实际上是"复制 + 删除",大文件会很慢,中途中断可能留下半个文件。所以几百 G 的数据迁移,我不会用mv,而是rsync -a --remove-source-files,可断点续传、可校验、可限速。

rsync -av --progress --remove-source-files /data/old/ /data/new/

-a保留了权限、时间戳、软链接等属性,--remove-source-files在传输成功后才删源文件,比mv稳得多。

2.3 chmod、chown、umask:权限数字是怎么算出来的

权限这块,很多人是靠查表活着的:777 是啥、755 是啥、644 是啥。查表当然能干活,但一旦遇到umask或者"为什么我新建的文件是 664 不是 644"这种问题就卡住了。其实规则非常简单。

权限分三组:属主(u)、属组(g)、其他(o)。每组三个位:读(r=4)、写(w=2)、执行(x=1)。三个位相加就是一个八进制数字。所以:

  • 7= 4+2+1 = rwx
  • 6= 4+2 = rw-
  • 5= 4+1 = r-x
  • 4= r--
  • 0= ---

chmod 755 file就是属主 rwx、属组 r-x、其他 r-x。这个算法熟了你就不需要查表了。

chmod 644 config.yml # 普通配置文件:属主可写,其他人只读 chmod 755 run.sh # 脚本:属主可执行,其他人可读可执行 chmod 600 id_key # 私钥类文件:只有属主能读写 chmod 700 /data/private # 私有目录:其他人连进都进不去

chown改属主和属组,格式chown 用户:组 文件,配合-R递归:

chown -R www-data:www-data /var/www/site

有个细节容易忽略:chown -R递归时,如果目录下有软链接,默认是不跟随的(改的是链接本身)。要跟随就用-h的反面——注意 GNU 版本下chown默认行为是不跟随软链接,而chmod -R遇到软链接也不跟随。这个在部署时踩过一次:一批软链接指向的文件权限没被改到,服务启动时读不了,排查了半天才发现是递归没跟过去。

umask是"默认权限的掩码",这个概念最容易绕晕。规则是:新建文件的默认权限 = 666 - umask;新建目录的默认权限 = 777 - umask。

umask # 查看当前值,通常是 022 或 002 umask 022 # 设置:文件 644,目录 755 umask 077 # 设置:文件 600,目录 700(私有,最严格)

为什么文件基数是 666 而不是 777?因为新建文件默认不给执行位——防止你写了个文本文件,结果因为权限配置被当程序执行了。这个设计是刻意的。

有了这个公式,"为什么我新建的文件是 664"就能自己算出来:umask是002(某些发行版对用户组内的成员更宽松),666 - 002 = 664,组内成员也能写。这个值在多人协作的共享目录里是合适的,但在生产环境里往往过于宽松,值得检查一遍。

3. 文本处理三剑客:日志排查的主力

服务器上没有 IDE,没有图形化的日志查看器,你能依赖的就是grep、sed、awk加上sort、uniq、wc这几个小工具。这一节围绕"一次完整的线上问题排查"来讲,而不是逐个讲语法。

3.1 grep:先缩小范围,再谈分析

排查日志的第一原则是:永远不要直接cat一个几百兆的日志。先缩小范围。grep就是干这个的。

grep -c "ERROR" app.log # 先数一下有多少条 grep -n "ERROR" app.log | head -20 # 带行号看前 20 条 grep -rn "NullPointer" /var/log/app/ # 递归搜目录,带行号 grep -v "DEBUG" app.log # 反向匹配:排除噪音 grep -i "timeout" app.log # 忽略大小写 grep -A 5 -B 5 "ERROR" app.log # 前后各带 5 行上下文

-A/-B/-C这三个参数是排查日志的关键。异常栈是一段一段的,只匹配到Exception那一行等于什么都没看到,必须把上下文带出来。-C 10表示前后各 10 行,一般够用。

几个实战组合值得记住:

# 找出所有非 200 状态码的请求,统计各状态码分布 grep -oE "status=[0-9]{3}" access.log | sort | uniq -c | sort -rn # 找出某个时间窗口内的错误 grep "2024-05-20 1[0-2]:" app.log | grep "ERROR" # 排除健康检查和使用内部探针的请求,看真实流量 grep -v -E "(health|ready|metrics)" access.log | grep " 500 "

第二条里1[0-2]是正则的字符区间,表示 10 点、11 点、12 点三个小时。这个技巧在处理按小时切分的日志时特别顺手。

注意:正则里的[在 shell 中有通配符含义,虽然放在引号里就安全,但更稳妥的习惯是所有正则都加单引号。grep '[0-9]' file永远比grep [0-9] file安全,后者在当前目录恰好有名为9的文件时会展开成文件名。

还有一个实践中的取舍:如果机器上装了ripgrep(命令名rg),在大量文件中递归搜索时优先用它,速度快一个数量级,而且默认遵守.gitignore。但要注意它的默认行为差异——rg默认跳过隐藏文件和二进制文件的匹配方式与grep -r不同,脚本里依赖固定输出时还是用grep更可控。

3.2 sed 与 awk 的分工边界

这两个工具经常被混用,其实分工很清楚:

  • sed是流编辑器,擅长"按行做替换、删除、插入",输入输出都是文本流,不擅长结构化计算。
  • awk是文本处理语言,擅长"按列取字段、做条件判断、求和计数"。

判断该用哪个,看你的动作是不是跟"列"有关。只改字符串内容,用sed;要按字段统计,用awk。

sed最常用的就三类操作:

# 1. 替换:把文件里的 old 换成 new,加 g 表示每行全部替换 sed -i 's/old/new/g' config.txt # 2. 打印指定行范围 sed -n '100,200p' app.log # 3. 删除包含某关键字的行 sed -i '/^#/d' config.txt # 删掉注释行

sed -i的坑必须说:它直接修改原文件,没有备份。GNU sed 支持-i.bak先备份再改,我强烈建议养成这个习惯:

sed -i.bak 's/8080/9090/g' app.conf

一个真实翻车案例:有人写sed -i 's/old/new/g' *,当前目录里有个软链接指向生产配置,sed跟随软链接把生产文件改了。所以sed -i通配全目录这种操作,先ls *看一遍目录内容,或者加--follow-symlinks的反面(默认不跟随,但不同实现有差异),别赌。

替换内容里含/的时候,s/old/new/会语法错乱,比如替换路径。解法是换分隔符,sed允许用任意字符做分隔:

sed -i 's#/usr/local/old#/usr/local/new#g' app.conf

awk的核心是awk '模式 { 动作 }',默认按空白分列,$1是第一列,$NF是最后一列。

# 取第 1 列和第 3 列 awk '{print $1, $3}' data.txt # 按指定分隔符(冒号)取第 7 列,去重 awk -F: '{print $7}' /etc/passwd | sort -u # 条件过滤 + 求和:统计 500 错误的总字节数 awk '$9 == 500 {sum += $10} END {print sum}' access.log # 按某列分组统计 awk '{count[$1]++} END {for (k in count) print k, count[k]}' access.log

-F:这个写法要熟,因为/etc/passwd、/etc/group、以及大量日志格式都是冒号或特定字符分隔的。$NF表示最后一列,$(NF-1)是倒数第二列——处理字段数不固定的行时特别好用。

3.3 一次完整的日志排查链路实录

光讲单个命令没意义,我把一次真实的排查过程还原一遍。场景是某项目接口在凌晨出现间歇性超时,需要从日志定位原因。

第一步:确定问题时间窗口。先看错误总数和分布,判断是持续性还是尖峰式。

grep -c "timeout" app.log grep "timeout" app.log | awk '{print substr($1,1,13)}' | sort | uniq -c

这里substr($1,1,13)取时间戳前 13 位(形如2024-05-20 10),把秒级时间聚合成小时,uniq -c出来的一列数字就是每小时的超时数。尖峰在哪一小时,一目了然。

第二步:看尖峰时段的具体请求。缩小到那一小时,把耗时的和超时的都捞出来。

grep "2024-05-20 03:" app.log | grep -E "(timeout|cost=[0-9]{4,})" | head -50

cost=[0-9]{4,}匹配耗时四位数以上(毫秒计即 1 秒以上)的请求。-E开启扩展正则,|表示或。

第三步:找出慢请求的共同特征。假设日志格式里第 7 列是接口路径。

grep "2024-05-20 03:" app.log | awk '$10 > 1000 {print $7}' | sort | uniq -c | sort -rn | head

这一条把"3 点这一小时内、耗时字段大于 1000 的请求"的接口路径提取出来,按出现次数倒排。前三名就是重点怀疑对象。

第四步:看这几个接口当时的调用量和并发。

grep "2024-05-20 03:" app.log | grep "/api/order/query" | wc -l

如果这个接口在这一小时的调用量是平时的十倍,基本可以确定是流量突增;如果调用量正常但慢请求集中,那更像外部依赖(数据库、下游服务)在抖动。往下查就是看同机其他日志,或者用第 4 节的进程诊断命令看系统负载。

这套流程的价值在于每一步都在收缩范围:全量日志 → 一小时 → 一类错误 → 一个接口 → 一个数量级判断。全程没有cat整个文件,都是在流上操作,几百兆的日志也是秒级响应。

心得:日志字段顺序一定要先搞清楚再写awk。我踩过的坑是把耗时字段的列号记错,白白分析了半小时才发现统计的是一列无关的数字。先用head -3 app.log加awk '{for(i=1;i<=NF;i++) print i, $i}'把每列标出来,比凭记忆靠谱。

4. 进程、资源与端口:现场诊断

文件操作是"日常",进程和端口诊断才是"救火"。这类命令的特点是有明确的检查顺序,乱敲会浪费时间。我给自己定的顺序是:先看整体负载,再看具体进程,最后看进程在等什么。

4.1 ps、top、pidstat:从全局到单个进程

top是入口,但它的默认交互体验对新手不友好。先用几个一次性命令把宏观情况摸清楚。

uptime # 输出:10:30:00 up 30 days, 2:10, 3 users, load average: 1.20, 0.85, 0.60

load average三个数字分别是 1 分钟、5 分钟、15 分钟的平均负载。判断标准是拿它和 CPU 核数比:4 核机器负载 4 大约是满载,长期超过 4 就说明有排队。看趋势比看绝对值重要——从右往左看,如果是递减的(15 分钟 > 5 分钟 > 1 分钟),说明负载在回落;如果是递增的,说明问题正在发生。

# 按 CPU 倒序取前 10 ps aux --sort=-%cpu | head -11 # 按内存倒序 ps aux --sort=-%rss | head -11 # 看进程树,理清父子关系 ps -ef --forest | grep -A 5 "某服务名"

--sort这个参数我强烈推荐,比ps aux | sort -k3 -rn更直观,字段名直接写,不用记列号。ps aux输出里%CPU是第 3 列、%MEM是第 4 列、RSS(实际物理内存)是第 6 列,但不同实现有差异,用--sort一劳永逸。

进程树在实际排查中价值很高。如果某个主进程下面挂了 50 个子进程在抢占 CPU,说明是主进程"放得太开"。

pidstat不在最小化安装里,需要装sysstat包,但它的价值完全值这个麻烦——它给的是时间区间内的增量,,而不是top那种"采样瞬间"。这两个信息的含义区别很大。

# 每 2 秒采样一次,共 5 次,看某个进程的 CPU 和内存 pidstat -p 1234 2 5 # 看所有进程的磁盘 IO,找出读写大户 pidstat -d 2 5

top里显示的%CPU是自上而下的采样估算,某些短时突发的高占用会被平滑掉;pidstat在区间内累加,更能反映真实消耗。有一次排查"某个进程看起来 CPU 不高,但机器整体负载很高",就是靠pidstat -d发现它在疯狂做磁盘 IO——CPU 全在iowait上,进程本身的用户态 CPU 确实不高。

这也引出一个基本判断:看 CPU 高之前,先看是真算还是干等。top里%wa(wait)和%id(idle)两个值很关键。如果%wa持续很高,说明 CPU 在等磁盘;如果%id高但负载也高,那可能是大量进程在等锁或者 IO。

4.2 lsof 与端口占用排查

"端口被占用"是开发环境最高频的问题之一。标准解法是三步:

# 1. 看端口被谁占了 ss -lntp | grep 8080 # 2. 如果输出里有 PID,直接看这个进程 ps -fp 1234 # 3. 确认后决定是杀还是换端口 kill -15 1234 # 先礼 kill -9 1234 # 后兵

ss参数解释:-l只看监听状态,-n不做域名解析(快),-tTCP,-p显示进程(需要 root 权限才能看到别人的进程)。老系统上可能只有netstat,对应的写法是netstat -lntp。

# 看所有已建立的 TCP 连接及其状态统计 ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

这一条的输出能直接看出连接状态分布。ESTAB是正常连接数量,TIME-WAIT是主动关闭方留下的,CLOSE-WAIT大量堆积则说明有一方没正确处理关闭——这通常是应用层代码的问题,不是网络问题。

lsof的信息维度更宽,它能列出进程打开的一切:文件、目录、网络连接、设备。

lsof -p 1234 # 这个进程打开了啥 lsof -i:8080 # 谁在用 8080 端口 lsof +D /data/uploads # 哪些进程在用这个目录(递归,很慢) lsof /data/app.log # 谁占着这个文件

最后一个用法解决过一个经典问题:日志文件被删了但磁盘空间没释放。原因是某个进程还持有文件句柄,df显示磁盘满,du加起来却对不上。用lsof | grep deleted能找出这类"幽灵文件",处理方法是重启对应进程(或者用> /proc/PID/fd/N清空)。

4.3 磁盘与内存:df、du、free 的正确打开方式

磁盘排查有一条经验公式:df看总量,du找大户,lsof抓幽灵。

df -h # 各分区使用率 du -sh /data/* 2>/dev/null | sort -rh | head -10 # /data 下前 10 大目录 du -ah /data/logs | sort -rh | head -20 # 深入到文件级

du -sh加2>/dev/null是为了屏蔽权限不足的报错噪音,很有必要——不然你在根目录跑一次,屏幕会被刷满。

free有个新手最常问的问题:"为什么 available 和 free 差那么多?"因为 Linux 会把空闲内存拿去做缓存(buff/cache),这是设计如此,不是内存泄漏。真正该看的是available那一列——它表示"还能分给新应用的内存",是包含可回收缓存的。free接近 0 但available充足是完全正常的。

free -h # 重点看 available 这一列

如果真要清缓存(一般不建议,除了压测场景需要冷启动环境):

sync && echo 3 > /proc/sys/vm/drop_caches

这条命令会丢弃页缓存、目录项和 inode 缓存,执行后系统短期内会变慢(要重新从磁盘读)。生产机上不要为了"让内存数字好看"去执行,缓存本来就是花内存换性能。

5. 把命令串起来:管道、重定向与脚本

单个命令只是零件,真正的生产力来自组合。这一节讲管道和重定向里那些"看着简单但细节很多"的地方,以及怎么把一串常用命令固化成一个能复用的脚本。

5.1 管道与重定向:几个必须精确的细节

管道|把前一个命令的标准输出接到后一个命令的标准输入。重定向>、>>、2>、&>、<则是把三个标准流(stdin=0,stdout=1,stderr=2)接到文件上。

最容易出错的是>和>>的区别,以及 stderr 的处理:

cmd > out.log 2>&1 # 标准输出和错误都写进 out.log(覆盖) cmd >> out.log 2>&1 # 追加 cmd &> out.log # bash 的简写,等价于上面第一行 cmd 2>/dev/null # 丢弃错误输出

2>&1的顺序有讲究。cmd > out.log 2>&1是对的:先把 stdout 指向文件,再让 stderr 复制 stdout 的指向。如果写成cmd 2>&1 > out.log,stderr 会复制"当时"的 stdout(终端),然后再把 stdout 指向文件——结果就是错误还是打在屏幕上。这个顺序问题我在别人的脚本里见过太多次。

还有一个非常实用的重定向技巧:同时输出到屏幕和文件。

cmd 2>&1 | tee out.log cmd 2>&1 | tee -a out.log # 追加模式

tee命令得名于水管的三通接头,非常形象。做耗时任务时用它,既能看到实时进度,又留下完整记录。

>有个隐蔽的危险:它会在命令执行之前就清空目标文件。所以cat file > file会得到空文件,因为打开的一瞬间原内容就没了。安全写法是用临时文件中转,或者用sponge(moreutils 包提供):

cat file | sponge file

管道还有个默认行为值得知道:管道中的每个命令都在子 shell 里运行。这意味着在管道里改变量,出了管道就没了:

count=0 echo "a b c" | while read -r x; do count=$((count+1)); done echo $count # 输出 0,不是 3

这是新手写脚本最经典的坑之一。解法是避免在管道里累积变量,或者用进程替换(while ... done < <(command)),或者让整个 pipe 在同一个 shell 里执行。

5.2 shell 脚本骨架与 set 三件套

把命令固化下来的第一步是写脚本。我的脚本开头固定这几行:

#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t'

这四行不是仪式,每一行都在挡一类事故:

  • set -e:任何命令返回非零就立即退出。不加这个,脚本中间失败了会继续往下跑,可能造成更严重的后果。
  • set -u:使用未定义变量时报错退出。这条是防rm -rf "$DIR/"里DIR没赋值那种灾难的。
  • set -o pipefail:管道中任一环节失败,整个管道就返回失败。不加的话,管道的退出码只看最后一个命令,grep xxx nofile | wc -l会返回 0,因为你只判断了wc。
  • IFS=$'\n\t':把字段分隔符设为换行和 Tab,避免文件名带空格时被拆成多个词。

最后一条在批处理文件时是必须的:

# 危险:文件名有空格就废 for f in $(ls *.log); do echo "$f"; done # 安全:用 glob 直接展开 for f in *.log; do echo "$f"; done # 更安全:含子目录 while IFS= read -r -d '' f; do echo "$f"; done < <(find . -name "*.log" -print0)

-print0配合read -d ''是处理任意文件名(包括含空格、换行)的标准姿势。这个组合看着别扭,但它是唯一不会出错的方式。

一个完整的实用脚本骨架长这样:

#!/usr/bin/env bash set -euo pipefail LOG_DIR="/data/logs" RETENTION_DAYS=7 usage() { echo "用法: $0 [-d 日志目录] [-n 保留天数]" exit 1 } while getopts "d:n:h" opt; do case "$opt" in d) LOG_DIR="$OPTARG" ;; n) RETENTION_DAYS="$OPTARG" ;; *) usage ;; esac done [[ -d "$LOG_DIR" ]] || { echo "目录不存在: $LOG_DIR" >&2; exit 1; } echo "清理 $LOG_DIR 下 $RETENTION_DAYS 天前的日志..." find "$LOG_DIR" -type f -name "*.log" -mtime "+$RETENTION_DAYS" -print -delete

注意-print -delete的顺序:先打印再删除,删除的每一条都会显示出来,事后有据可查。

5.3 crontab:定时任务的三个隐形坑

crontab -e编辑当前用户的定时任务,格式是五段加命令:

分 时 日 月 周 命令 0 3 * * * /opt/scripts/clean.sh >> /data/logs/clean.log 2>&1

第一个坑:环境变量不继承。crontab执行时用的是极简环境,PATH通常只有/usr/bin:/bin,你在~/.bashrc里配的所有东西都不生效。所以脚本里最好全用绝对路径,或在 crontab 顶部显式定义:

PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash

第二个坑:工作目录不确定。默认在用户家目录,不是脚本所在目录。脚本里依赖相对路径必挂。所以脚本里要么用绝对路径,要么开头先cd到目标目录。

第三个坑:输出没地方去。cron 执行失败会尝试发邮件,但大多数机器没配邮件服务,报错就静默消失了。所以每条任务都加上>> 日志 2>&1,出问题能查。

还有一点容易忽略:%在 crontab 里是特殊字符(表示换行),如果命令里有date +%Y%m%d,必须转义成\%:

0 3 * * * /opt/scripts/backup.sh "$(date +\%Y\%m\%d)" >> /data/logs/bak.log 2>&1

这个坑不踩一次很难记住。想绕开它,可以把这个逻辑挪进脚本内部,crontab 里只写0 3 * * * /opt/scripts/backup.sh,干净得多。

6. 常见问题速查与踩坑清单

这一节是纯粹的经验汇总,都是我在实际环境里反复遇到的东西。

6.1 报错速查表

报错信息常见原因排查动作
command not found没装 / 不在 PATH / 拼错type -a 命令,echo $PATH
Permission denied缺执行位 / 缺读权限 / 目录无 x 位ls -l看权限,检查每级目录
No such file or directory(文件明明存在)脚本的 #! 解释器路径错了head -1 script.sh,file script.sh
Text file busy文件正在被运行lsof 文件,先停进程再覆盖
Argument list too long通配符展开的文件太多改find ... -exec或xargs
Too many open files文件描述符上限到了ulimit -n,cat /proc/PID/limits
No space left on device磁盘满 / inode 满df -h,df -i,lsof | grep deleted
Cannot allocate memory内存或 overcommit 限制free -h,dmesg | tail
Address already in use端口被占ss -lntp | grep 端口
Bad interpreter脚本在 Windows 下编辑过,行尾是 CRLFdos2unix或sed -i 's/\r//'

倒数第二条的df -i值得单独提一句。磁盘空间没满但写不进去,很可能是 inode 用完了——大量小文件场景(邮件队列、缓存目录、session 文件)特别容易碰到。df -h显示有空间,df -i显示 100%,那就得去清小文件,删大文件没用。

倒数第一条也是高频问题:脚本在本地编辑器写好、传到服务器上就跑不了,报bad interpreter。原因是行尾符是 CRLF 而不是 LF。用file script.sh一看,输出里带with CRLF line terminators就确诊了。

6.2 我自己的几条铁律

第一,破坏性命令先降级执行。rm换成ls,find -delete换成-print,sed -i换成不带-i先看输出。三秒钟的成本,换的是不可逆操作的安全边际。这个习惯我保持了七八年,中间救回来至少四五次。

第二,执行前先确认自己在哪、以谁的身份。有一组命令我几乎形成了条件反射:

whoami && pwd && hostname

看起来啰嗦,但多开几个终端窗口的时候,你真的会搞不清这个窗口连的是测试机还是生产机。尤其是用同一套终端配色的时候。我现在的做法是给不同环境的 shell 提示符配不同颜色,PS1里带上主机名,视觉上就不会混。

第三,任何"一次改一批"的操作,先拿一个样本试。批量替换配置文件、批量改权限、批量重命名,先对单个文件执行,验证输出正确,再上全量。

# 先对一个文件试 sed 's/old/new/g' config-01.conf | diff config-01.conf - # 确认 diff 输出符合预期,再批量 find . -name "config-*.conf" -exec sed -i.bak 's/old/new/g' {} +

| diff 原文件 -这个用法很好用,-表示标准输入,能直接对比"改完的样子"和"现在的样子",比人眼扫一遍可靠得多。

第四,多用man和--help,但别指望从头读到尾。man页面的正确用法是搜索:进去之后按/输关键字,n跳下一个。比如查find的时间参数,直接man find然后/mtime,比在网上搜一堆互相抄的文章快且准。

第五,命令的历史记录是你的资产。Ctrl+R反向搜索历史命令,配合history | grep 关键字,比重新敲一遍快得多。我还会定期把一些复杂的组合命令记到一个~/notes/cmd.md里,按场景分类。真正高频的那二三十条,用着用着就刻进肌肉记忆了,剩下的查笔记就行——没必要追求"全部记住",那是背单词的思路,不是用工具的思路。

最后分享一个我最近才养成的小习惯:任何在终端里一次敲超过三行的复杂操作,我都会先在/tmp下建个.sh文件写清楚再执行。一是能改,二是留下记录,三是下次直接复用。终端里一口气敲完看似帅,但出了错要整段重来,还不容易复盘。慢一点,稳一点,实际上更快。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 5:45:20

数据结构与算法实战:栈队列实现与应用解析

1. 数据结构与算法实战&#xff1a;从基础栈队列到经典问题解析在算法竞赛和编程面试中&#xff0c;栈、队列以及它们的变体是最基础也最常用的数据结构。很多复杂问题都可以通过巧妙运用这些基础结构来解决。本文将带大家从零开始实现基础的栈和队列&#xff0c;逐步深入到括号…

作者头像 李华
网站建设 2026/11/6 5:45:18

西门子PLC恒压供水系统设计与PID控制实现

1. 项目背景与核心需求在工业自动化领域&#xff0c;恒压供水系统是保障生产生活用水稳定的关键基础设施。传统供水系统存在压力波动大、能耗高、响应慢等问题&#xff0c;而采用西门子PLC控制的恒压供水系统能够实现精确的压力调节和智能化运行。这个项目的核心目标是开发一套…

作者头像 李华
网站建设 2026/11/6 5:44:39

TTAO-BP-KDE混合算法在区间预测中的应用与优化

1. 项目背景与核心价值在工业预测和金融分析领域&#xff0c;传统的点预测方法往往难以应对复杂系统中的不确定性。这个项目实现了一种融合三角拓扑聚合优化&#xff08;TTAO&#xff09;、BP神经网络和核密度估计&#xff08;KDE&#xff09;的混合算法&#xff0c;专门解决多…

作者头像 李华
网站建设 2026/11/6 5:43:11

010 Editor二进制解析:模板与脚本定位配置块

简介&#xff1a;这是一套面向开发人员、系统管理员及逆向与数据分析学习者的010 Editor十六进制编辑工具包&#xff0c;重点解决二进制文件查看修改、大文本检索替换和复杂文件结构解析等需求。压缩包共35个文件&#xff0c;约19.64MB&#xff0c;以dll运行库、exe主程序、qhc…

作者头像 李华
网站建设 2026/11/6 5:42:55

分布式锁原理与实现方案全解析

1. 分布式锁核心概念解析在分布式系统中&#xff0c;多个服务实例对共享资源进行并发访问时&#xff0c;分布式锁是保证数据一致性的关键机制。与单机环境下的线程锁不同&#xff0c;分布式锁需要解决网络延迟、节点故障等特有挑战。典型的应用场景包括&#xff1a;秒杀系统中的…

作者头像 李华
网站建设 2026/11/6 5:42:55

数据结构与算法:提升开发效率的核心技术

1. 为什么数据结构与算法如此重要记得刚入行时&#xff0c;我接手过一个看似简单的用户推荐系统优化任务。原本运行良好的系统随着用户量增长突然变得异常缓慢&#xff0c;查询延迟从200ms飙升到5秒以上。经过三天三夜的排查&#xff0c;最终发现问题出在一个嵌套循环的匹配算法…

作者头像 李华