接手一台旧服务器时,我遇到过一件挺诡异的事:df -h显示根分区已经用了 92%,但我在/home、/var、/opt之间来回翻,du累加出来的结果硬生生比df少了十几个 G。当时怀疑日志被删过,后来问了一圈才知道,之前的运维用rm清过一次大日志,但对应的服务进程一直没重启——文件虽然从目录里“消失”了,句柄还握在进程手里,磁盘空间自然也不会还给你。
Linux 下的文件操作,真正难的不是背下几十个命令,而是搞清楚文件名、文件内容、磁盘空间三者之间的关系。很多人以为文件操作就是ls、cp、mv、rm那几板斧,实际上底层逻辑通了,遇到再花哨的需求都能自己想出解法。这篇博文不打算按命令手册的顺序复读,而是沿着“文件在 Linux 里到底是什么、日常操作命令怎么用、内容怎么查、权限怎么管、磁盘被占怎么查”这条线,把我踩过的坑和验证过的方法一次讲透。
1. 文件名只是门面:inode和目录项才是文件的本体
1.1 从一次“磁盘神秘占用”事故说起
先回到开头那个事故。df是站在文件系统视角看的,它统计的是整个文件系统里已分配的数据块;而du是遍历目录树,把每个当前可见的文件大小累加出来。两者天然就有差异来源,最常见的一个就是“文件被删了,但进程没释放”。
用rm删一个文件,系统做了什么?它只是把目录里的那条记录(目录项)抹掉了,同时把 inode 的链接计数减一。如果这个文件正被某个进程打开着,那么 inode 本身和它对应的数据块依然幸存,进程通过自己的文件描述符还能继续读写。这时候df看到那些块还是“已被占用”,但du顺着目录已经找不到这个文件了,所以df比du大。处理办法是找到那个进程并重启,或者直接清空它的文件描述符。
# 查看已删除但还占着空间的文件 lsof | grep deleted # 或者更具体一点 lsof +L1+L1的意思是列出 link count 小于 1 的文件,也就是已经没有名字但还被进程持有的文件。找到 PID 之后,确认是哪个服务在占用,重启服务或让进程重新打开文件,空间才会真正释放。这个操作我后来用了很多次,几乎每次“磁盘满了但找不到大文件”的排查都绕不开这一步。
1.2 inode、目录项、数据块的关系
要理解文件操作,得先建立起三个概念,它们之间完全是分工协作的关系:
- 目录项(dentry):目录里的一条记录,保存“文件名 -> inode 编号”的映射。可以理解成图书馆的检索卡片。
- inode:文件的“档案卡”,记录文件类型、权限、属主、大小、时间戳,以及数据块在磁盘上的位置。Linux 里 inode 数量是有限额的,格式化时就有上限,这也是为什么有些文件系统会出现“空间还没用完但 inode 用尽”的怪现象。
- 数据块(data block):真正存放文件内容的物理区域,好比书架上的书页。
一次普通读操作,大致是:进程通过/home/user/xxx.txt这个路径,先找到/的目录项,再找到home目录的 inode,进入目录内容后匹配user,一级一级查到xxx.txt的目录项,最终拿到 xx 号 inode,再根据 inode 指向的数据块去读内容。
这个模型能解释很多现象:新建文件为什么比写大量数据快?因为通常只需要分配一个 inode 和几个数据块,写目录项;移动文件为什么不一定慢?因为只要在同一文件系统里,改的可能只是目录项,文件内容根本没动。后面章节会反复用到这套模型。
1.3 硬链接与软链接:一字之差,底层完全不同
明白了 inode 之后,硬链接和软链接的区别就非常清晰了。
- 硬链接:同一个 inode 再增加一个目录项。
ln a.txt hard.txt之后,两个文件名指向同一个 inode 编号,inode 的链接数变成 2。删除任意一个,另一个依然可用,因为链接计数只是减一,只要不等于零,数据和 inode 就还活着。 - 软链接(符号链接):新建一个独立的 inode,内容存的是目标路径的字符串。
ln -s a.txt soft.txt后,soft.txt的 inode 指向的是一串路径文字。删除a.txt,soft.txt就变成“悬空链接”,访问它会报 No such file or directory。
用ls -li可以一眼看穿:硬链接的两个文件名 inode 号相同,软链接会显示一个全新的 inode,而且ls -l里能看到->指向目标。
这里有个高频面试点:硬链接不能跨文件系统。因为 inode 编号只在当前文件系统内有意义,另一个文件系统的 xx 号 inode 完全是另一回事,也没法直接承接同一个链接计数的生命周期。软链接只是个路径字符串,所以跨文件系统压根不受影响。
1.4 为什么 mv 很快、cp 却慢,rm 后空间为何不立即释放
这节把底层模型直接映射到三个高频操作上。
mv old new在同一文件系统内,只是修改目录项,把文件名从old换成new,inode 和数据块都不动。因此不管文件是 1K 还是 50G,瞬间就能完成,唯一可能变慢的是如果目标路径跨越了文件系统边界——那时mv不再具备“改目录项”的优势,内核会实打实地把数据复制过去再删掉原文件,本质上退化成copy + unlink。
cp则是创建新文件的过程:分配新 inode,逐个数据块读源、写目标,再更新目录项。数据量越大,时间越长。想保留属性时要用cp -p或cp -a,其中-a是归档模式,等于-dR --preserve=all,连符号链接、权限、时间戳、属主都会带上,日常备份我直接默认cp -a。
rm也不像表面那么无害。它只是把目录项清零、inode 链接数减一,并没有立刻把数据块“抹零”。所以被删的数据在专业工具下还能恢复——前提是 inode 没有被重新分配、数据块没有被覆盖。对于进程正在使用的文件,rm之后空间更是完全不会释放,直到进程关闭文件描述符或结束。理解了这一点,很多“删了文件但磁盘没变化”的疑问都能迎刃而解。
2. 日常文件操作命令:参数背后都有设计理由
2.1 创建文件的几种方式:touch 与重定向的差别
touch a.txt是很多人学的第一个“建文件”命令,但 touch 的真正用途是更新时间戳。如果文件不存在,它会顺手创建一个空文件;如果文件已存在,它默认只是把 atime/mtime 刷新成当前时间。想精确指定时间可以用touch -t 202401011200 a.txt,备份脚本里回写时间戳时特别好用。
另外一个创建文件的常见路径是重定向:
# 创建空文件,等价于 touch > newfile.txt # 覆盖内容 echo "hello" > newfile.txt # 追加内容 echo "world" >> newfile.txt这里有个新手容易忽略的点:>是截断创建,>>是追加创建。如果文件本来就有内容,>会先把文件清空再写。运维场景中常用: > /var/log/xxx.log或echo > /var/log/xxx.log来快速清空日志而不删除文件本身,这样占着日志文件的进程不需要重启,句柄依然有效,磁盘空间也会逐步被新内容覆盖。
2.2 cp 的参数选择:-r、-p、-a 谁适用谁
cp最常踩的坑是“复制目录忘加-r”。因为 cp 默认只处理文件,遇到目录会直接报omitting directory。加上-r后递归复制,但默认不保留时间戳、属主等属性。所以复制单文件一般用cp -p,把权限和时间戳一并带上;复制整个目录树直接用cp -a,一劳永逸。
再补充一个少有人注意的点:cp -r对软链接的处理不太一样。普通cp -r会把软链接指向的目标文件内容复制过去,而不是复制软链接本身;cp -a则能保持软链接。如果你有一整个目录塞满了符号链接,用错参数会复制出一堆实体文件,目录体积暴涨。判断方式也简单:
cp -r /opt/apps /backup/apps cp -a /opt/apps /backup/apps # 对比两次的 du -sh 或 ls -l 后区别一目了然2.3 mv 跨文件系统时,先读后删,中途断掉很尴尬
文件系统的边界是不可见的,mv命令也从不主动告诉你它是否跨了设备。以前我把一个 20G 的数据库备份文件从家目录mv到/data分区,结果等了老半天,才意识到这是在跨分区“搬运”,实际执行的是复制加删除。
一个有效的判断方式:
# 查看源和目标是否在同一文件系统 df -h /home/backup.tar df -h /data/backup.tar # 或者直接查看 inode 归属 stat -f -c '%d' /home/backup.tar stat -f -c '%d' /data/backup.tarstat -f里的%d是文件系统的设备编号,两者相同才算同一文件系统。跨分区移动大文件时,我一般改用rsync,它可以断点续传、显示进度,等同步完了再删源文件,比裸mv稳得多。从这个角度看,mv的“快”只在同分区内成立。
2.4 rm 的“送命题”与横杠文件的删除
rm -rf /这类事故不多赘述,但有一类命令写法上的坑值得专门记:文件名以横杠开头时,命令会把它当成参数。比如目录里有个文件叫-abc.txt,直接rm -abc.txt会被解析成一系列选项,报错或者什么都做不了。正确姿势是在文件名前加--,表示“后面都是普通参数,不是选项”:
rm -- -abc.txt ls -- -abc.txt同理,cp -- -sourcefile /tmp/destfile也能正确处理横杠开头的文件。这个小细节在写自动化脚本处理外部输入的文件名时特别重要,因为文件名不是你起的,防不胜防。
另外说一句rm -rf的别名保护。很多运维会习惯性加alias rm='rm -i',但我建议在管理机上只给当前用户加,不要在共享 root 环境中全局加,否则批处理脚本在几百台机器上执行时遇到交互提示会卡住,反而更危险。删重要数据前更好的习惯是:先ls看清楚路径,再du -sh记录当前占用,最后才执行删除,删完再df -h确认空间变化。
3. 文件内容查看与搜索:围绕日志和配置的实战套路
3.1 查看文件内容的效率选择:cat、less、tail、head
cat适合小文件,比如个位数的配置文件,超过几百行就不好用了。原因很简单:它会一次性把整个文件输出到终端,巨大的文件会把终端卡到崩溃,还可能刷屏刷到看不到头。几十兆的文件,我直接用less,它支持前后翻页、搜索,不一次性读入全部内容,内存占用低。常用快捷键是/搜索、n下一个匹配、G跳到末尾、g跳到行首。
head和tail分别取头和尾。查日志基本离不开tail -f,它的-f是 follow,文件有新内容就持续打印,排查接口报错、日志写入是否正常时非常好用。
# 最后 50 行 tail -50 app.log # 持续跟踪追加内容 tail -f app.log # 带行号查看配置中间段落 sed -n '30,50p' app.confsed -n那个用法很多人不知道,它比head | tail组合更精确,而且只打印指定行范围,不修改文件,适合快速看配置中间段。
3.2 grep 的效率与常用组合:搜文件、搜目录、统计次数
grep是内容搜索的第一主力。基本工作流是:在单个文件里搜关键字加行号,grep -n 'error' app.log;在整棵目录树里搜,加-r;只要文件名不要内容,加-l;统计匹配次数,加-c。
# 在 /etc 下递归搜索“Port 22”,只输出包含的文件名 grep -rl "Port 22" /etc # 统计 app.log 中 ERROR 出现的次数 grep -c "ERROR" app.log # 排除注释和空行,看配置真正生效的内容 grep -Ev "^#|^$" /etc/ssh/sshd_config管道组合是 grep 的灵魂。tail -f app.log | grep --line-buffered 'ERROR'可以实时过滤出日志中的错误行;ps aux | grep java | grep -v grep找出 Java 进程,-v排除掉 grep 本身那条。这里加--line-buffered是为了让 grep 按行刷新输出,否则它在管道里默认块缓冲,实时性会大打折扣。
3.3 sed 流编辑器:就地修改与安全的原地替换
sed 擅长“按行处理文本”。最常用的几个场景:
# 输出第 10 到 20 行 sed -n '10,20p' file.txt # 删除空行,输出到屏幕,不改文件 sed '/^$/d' file.txt # 原地替换,并先备份出 .bak sed -i.bak 's/127.0.0.1/0.0.0.0/g' /etc/myapp.conf-i是 in-place,直接改写文件。注意我给-i加了.bak后缀,意思是替换前先生成一个xxx.conf.bak备份文件。这个习惯来自一次事故:我写过一条范围过宽的 sed 替换,把配置文件里不该改的地方也改掉了,如果没有.bak文件兜底就只能凭记忆恢复。生产环境里凡是sed -i,请默认带上备份后缀,哪怕后来确认没问题再删掉备份,也比出问题后干瞪眼强。
3.4 一个日志排查的小综合案例
把上面几个工具串起来,就是一次漂亮的日志排查。假设应用在/var/log/myapp/下按天写日志,日志格式是2025-03-01 10:30:11 ERROR [订单模块] 下单失败。
# 1. 先看今天日志有多少 ERROR grep -c "ERROR" /var/log/myapp/$(date +%Y%m%d).log # 2. 列出 ERROR 出现的分钟级分布 grep "ERROR" /var/log/myapp/$(date +%Y%m%d).log | awk '{print $2}' | cut -d: -f1-2 | sort | uniq -c # 3. 找到出错的订单 ID grep "下单失败" /var/log/myapp/$(date +%Y%m%d).log | awk '{print $NF}' | sort | uniq -c | sort -rn第一步是看整体量级,第二步用awk '{print $2}抽出时间列,cut -d: -f1-2截到分钟,sort | uniq -c统计每个时间窗口的出错量,立刻能定位峰值时段。第三步$NF取每行最后一个字段,也就是订单 ID,再按次数排序,就能揪出高频失败订单。这套组合比我见过的许多商业监控面板还直观,尤其适合五分钟内就要定位问题的应急场景。
4. 权限与拥有者:挡在文件操作前面的第一道屏障
4.1 权限位的本质:三类对象、三种操作、一套数字
ls -l第一列那 10 个字符,是文件操作里最容易“假装看懂”的部分。格式拆解:第一个字符是文件类型(-普通文件,d目录,l符号链接),后面 9 个字符分成三组——属主(u)、属组(g)、其他用户(o),每组三位是 rwx:读、写、执行。
- 对普通文件:
r可读内容,w可修改内容,x可执行。 - 对目录:
r可列出目录里的文件名,w可创建/删除/重命名目录内的条目,x可进入目录或访问目录内文件。
这是一个极其重要的区分点:目录的 wx 和文件的 rwx 含义完全不同。很多人拿文件权限的逻辑套目录,就会出现“目录删不掉”的困惑。删除一个文件,看你有没有它所在目录的 w 权限,而不是看你有没有文件本身的 w 权限。
数字法换算很简单:r=4、w=2、x=1,每一组相加得出一个 0-7 的数字。rwxr-xr--就是 754。运维中常见的644是“属主读写,属组和其他只读”,755是“属主完全控制,其他人可读可执行”,对应目录和可执行文件。
4.2 chmod 的符号法和数字法,怎么选
chmod支持两种风格。数字法简洁、原子性好,适合批量设置;符号法更直观,适合只改一个类别的权限。
# 数字法 chmod 750 /opt/app # 符号法:给属主加执行权限 chmod u+x /opt/app/start.sh # 去掉属组的写权限 chmod g-w /opt/app/config.ini # 递归修改目录树 chmod -R 755 /var/www/site数字法有个坑:它是“覆盖式”的,会把你没有指定的位视为 0。也就是说原本775的文件,执行chmod 644之后,执行位全没了,目录对属组和其他人也失去了可进入权限,很可能导致程序打不开目录。所以在大规模chmod -R之前,最好先find看一遍目录结构和当前权限,别设完之后再挨个排查。
符号法的优点是有“增删”语义:u+x只是在原来基础上加一个执行位,不影响其他位。应急修权限时我更推荐符号法,比如网站目录突然 500,通常是权限不对,chmod -R u+rwX这种写法里大写的X表示“只有目录或已有执行权限的文件才加执行位”,避免给所有普通文件都加上 x。
4.3 chown:改拥有者之前,先想清楚用处
chown改变文件的属主和属组。常见的应用场景是:代码是从 root 用户部署的,但 Web 服务以www用户运行,导致写日志、写缓存目录时 permission denied。解法是:
# 修改属主为 www,属组为 www chown www:www /var/www/html -R # 只要属主 chown www /var/log/app.log # 只改属组 chown :www /var/log/app.log这里有个容易被忽略的知识点:普通用户只能把文件属主改给自己,不能改给别人;只有 root 能任意指定拥有者。所以很多提权类操作的前置条件都是“有 root 权限”。批量修改时注意-R的行为——它会递归处理目录下的所有内容,如果文件很多,执行时间不短,而且会把软链接目标和硬链接一并处理,需要权衡。不过对chown来说,通常-R是合理的:目录、子目录、文件全都要改成同一归属。
4.4 umask:新建文件为什么默认总是 644/755
你新建一个普通文件,ls -l一看通常是-rw-r--r--(644),新建目录则是drwxr-xr-x(755)。这是谁定的?是umask。
umask是“从默认权限中抹掉的权限位”。普通文件默认权限是 666,目录是 777,因为目录天然需要 x 才能进入。当前 umask 值是 022,换算逻辑是:
- 文件:666 去掉 022,得到 644。
- 目录:777 去掉 022,得到 755。
查看当前值直接输umask,修改是umask 002或umask 077。077比较严格,新建的文件对属组和其他人没有任何权限,适合包含密钥的私有目录。002常见于多人协作环境,组内可写,但要注意这也会让组外可读,安全要求高的系统不建议长期使用。
顺带一提,setgid对目录有一种特殊效果:目录加上 setgid 位后,文件在目录内新建时,文件属组自动继承目录的属组。协作开发的目录如果大家 group 不同,设置chmod g+s /shared/project后,新文件会自动归到共享组,省去频繁 chown。这是一个容易被忽略但极其实用的目录权限技巧。
5. 磁盘占用的排查链路:从 df 到 du 再到 lsof
5.1 df 和 du 列出的不一致,先别急着骂工具
df -h看全局,du -sh看局部,这两个命令在日常运维里几乎一起出现。很多人上来就du -sh /*想找罪魁祸首,结果发现所有目录加起来和df差一大截,就开始怀疑是“删不掉”的隐藏文件。实际上漏掉的大头,通常就是本章开头讲过的“已删除但被进程占用的文件”,以及挂载点本身。
排查顺序建议固定下来:
# 1. 全局视角 df -h # 2. 找出占用最大的挂载点目录 du -xsh /home /var /opt /tmp 2>/dev/null | sort -hr # 3. 逐级深入最大目录 du -xh --max-depth=1 /var 2>/dev/null | sort -hr # 4. 排查已删除但未释放的文件 lsof +L1第 2 步的-x是很多初学者不知道的关键参数,它表示只在当前文件系统内统计,别往下钻到其他挂载点里。如果没有-x,du /会把/proc、/sys、其他磁盘分区全部算进去,数值巨大且没有实际意义。
5.2 find 精准定位大文件和垃圾文件
df到du只能找出“哪个目录膨胀”,要找具体是哪几个文件,用find按大小、时间、类型过滤:
# 找到 /home 下大于 1G 的普通文件 find /home -xdev -type f -size +1G -exec ls -lh {} \; # 找到 /var/log 下 30 天前的日志 find /var/log -xdev -type f -mtime +30 -name "*.log" # 找到当前目录下的空文件并删除 find . -xdev -type f -empty -delete-size +1G是大于 1G 的意思,单位区分c(字节)、k(KB)、M(MB)、G(GB)。-mtime +30表示修改时间早于 30 天前,-mtime -1表示 24 小时内改过。-exec ls -lh {} \;是逐个执行,量多时有点慢,可以换成-exec du -h {} +批量统计。最后那个-delete我特别提醒一句:先打印列表确认,确认无误后再加-delete执行,别把-delete和-name条件写反了,一个条件写错就可能误删整片文件。
find里还有一个高频参数-xdev,等价于-mount,作用是禁止跨文件系统搜索。有了它就不会误入/proc、/sys这些虚拟文件系统,也不会扫到其他挂载的分区。排查磁盘占用时,find / -xdev ...是我固定不变的写法。
5.3 文件被删但空间不释放:lsof +L1 的完整处理
这一步是很多排障“最后一步”。前面df看到空间不足,du也找不出罪魁祸首,那就是时候上lsof了。
lsof +L1输出里会有一列SIZE/OFF和NODE,其中(deleted)标记格外显眼。拿到 PID 后:
# 看这个进程打开了哪些文件 ls -l /proc/PID/fd/ # 确认进程身份 ps -p PID -o pid,user,cmd处理方式通常是重启这个进程。如果它是个常驻服务,比如 Java 应用、Nginx,重启之后空间就会完全释放。如果一时不能重启,还有一种临时办法是清空对应的文件描述符:找到/proc/PID/fd/N中的被删文件句柄,用: > /proc/PID/fd/N把句柄指向的文件截断成 0。但注意,这个操作会导致进程后续写入的内容从头开始,可能破坏日志的连续性,我一般不推荐在非必要场景用,轻则日志错乱,重则程序崩溃。
5.4 大文件与小文件多的不同处理策略
同样是磁盘占用高,“一个大文件”和“一堆小文件”处理思路完全不同。一个大文件可能是日志、数据库备份、core dump,处理方式是确认是否可以删除/压缩;一堆小文件则常常落在缓存目录、临时目录、邮件队列,数量巨大时du也会变得奇慢,因为每个文件都要单独 stat 一次。
判断大小与数量的分布,可以这样:
# 目录里的文件数量统计 find /var/tmp -type f | wc -l # 目录里文件总大小 du -sh /var/tmp # 按文件个数排序差异化定位 find /var/tmp -type f | awk -F/ '{print $NF}' | wc -l大量小文件场景下,删除动作本身就会消耗大量时间,逐条rm极慢。可以用find /cache_dir -type f -delete让 find 自己遍历删除,比rm -rf批量更可控。顺便补充一个文件系统层面的知识点:如果分区里的 inode 耗尽,即使还有空闲空间也建不了新文件,df -i可以查看 inode 使用率。很多“磁盘没满但写不进去”的诡异故障,根源就在 inode 用尽,学会看df -i能省下一堆困惑时间。
6. 文件操作里的高频坑位与经典面试概念
6.1 rm -rf 的几个“极限操作”乱象
rm -rf是我见过事故率最高的命令,翻车形态五花八门。最常见的几类:
- 变量为空导致根目录遭殃:脚本里
base_dir=/data/app,后来变量没赋值,rm -rf ${base_dir}/logs变成了rm -rf /logs。规避办法是给base_dir加默认值和提前校验:
base_dir=${BASE_DIR:?变量未设置,终止执行}结尾斜杠的影响:
rm -rf /data/app/和rm -rf /data/app在绝大多数情况下等价,但如果拼到根目录就别提了。属于坏味道,干脆别写末尾斜杠。通配符意外展开:
rm -rf *.log在目录里有硬链接、隐藏文件时一般没事,但如果当前目录真的只有一个文件且叫*,通配符会匹配到它自己。这种情况不多,但自动化脚本里我不会依赖通配符删文件,通常先ls看一眼匹配范围。
写删除类脚本时,我现在习惯先干两件事:先find打印要删的文件列表,肉眼确认;再执行时用mv到临时回收目录而不是直接rm,确认无碍后再二次删除。这个“二级清理”的思路在个人博客和我朋友们的服务器上都验证过,真的能救回过一两次误删。
6.2 find 与文件名里的空格:不引号就翻车
文件名叫my notes.txt,处理时必须引号包全:
# 错误:空格被当成参数分隔 cat my notes.txt # 正确 cat "my notes.txt" # find -exec 里需要特别小心 find . -name "*.txt" -exec rm {} \;find -exec rm {} \;的{}被展开成带空格的文件名时,如果不用-exec而是xargs,还必须加-print0和-0配对,否则换行和空格会让 xargs 把文件名切碎:
find . -name "*.log" -print0 | xargs -0 rm这种“零字节结尾”的配对是处理文件名怪癖的标准姿势。批量操作文件之前,我建议用find -print0的方式统一处理,少踩翻车坑。
6.3 面试中关于“Linux 文件操作”的十个高频概念
热搜词里出现了“Linux 面试题”。这里按我的经验整理一套精炼版考点,不必死记,理解后脱口而出:
df 和 du 的区别:df 看文件系统整体使用的块数,du 遍历目录树统计当前可见文件大小。Delete 后未释放句柄会导致 df 大于 du 之和。
硬链接为什么不能跨文件系统:inode 编号只在本文件系统内有效,硬链接本质是给同一个 inode 增加目录项。
软链接被删导致的问题:悬空链接报 No such file or directory;写软链接时如果路径写法错误,可能权限错误导致很隐蔽。
mv 和 cp 的底层区别:同文件系统 mv 只改目录项;cp 要新建 inode、复制数据块。
目录的写权限 vs 文件的写权限:删除文件看所在目录的 w 权限,修改文件内容才看文件本身的 w 权限。
umask 022 为什么会产生 644 文件:666 与 022 的取反做按位与,得 644;目录同理,777 与取反运算得 755。
find 常见参数 -xdev、-size、-mtime、-name:分别控制文件系统边界、大小、时间、文件名。
lsof +L1 和 普通 lsof 的区别:普通 lsof 列所有打开文件,
+L1专门挑出 link count 为 0(已删除)但仍被占用的。chmod 数字法计算规则:rwx 分别对应 4、2、1,各组求和;v 8 进制意味着不会出现超过 7 的位。
日志轮转为什么要用 mv 而不是直接删除:mv 旧日志为新文件名,进程句柄仍指向原 inode,写完旧句柄再 reload/ reopen,才不至于丢失正在写的日志。这也是 daily cron 里的 logrotate 默认行为设计。
第 10 点其实很多刚入行的朋友没意识到:日志轮转时如果用rm删掉正在写的日志,进程的句柄还在原 inode 上,磁盘空间不会释放,而且tail -f也看不到新内容。正确的轮转姿势是mv app.log app.log.1然后让程序 reopen 日志文件,类似kill -USR1或重启服务。这也是上面所有底层知识的综合应用:文件名、inode、进程句柄、磁盘空间,一环扣一环。
6.4 实操中我最后想强调的几件事
- 不要在共享服务器上全局捆绑
alias rm='rm -i',批量脚本遇到交互提示会卡死;给每个用户单独配置就好。 - 大规模删文件前,先
du -sh记录现场,再用 find 打印列表,最后才执行删除。执行完再df -h验证变化。 cp -a、rsync -a里的-a永远是我的首选归档姿势,保留权限、时间、属主,避免复制完的服务因为权限问题起不来。- 写自动化脚本时,凡是涉及路径拼接,优先用绝对路径并用引号包住,避免变量为空时拼出根目录路径。
- 遇到
sed -i、perl -pi这类原地修改,默认先输出到.bak,确认无误再清理备份。
这些习惯谈不上什么高深技术,但都是实际操作里一点一点攒出来的“止损经验”。文件操作看着简单,Linux 的各种疑难杂症却往往藏在这些基础步骤里,能排查到哪一级、能想到哪一层,决定了你是被问题推着走,还是顺着文件系统的底层逻辑一步步把它揪出来。