我折腾服务器也有些年头了,从物理机、虚拟机再到容器,从 CentOS 6 一路追到 Rocky Linux 和 Ubuntu Server。坦白讲,Linux 文件系统这东西,看着基础,里头藏着的细节却能让运维半夜睡不着觉。
很多人会敲 cd、ls、rm,却没搞明白 inode 到底是个啥;知道 chmod 777 能“治好”权限问题,却不知道它顺手打开了多大的安全窗口;磁盘空间告警,一把 rm 下去,日志没了,可用空间还是纹丝不动。
这篇文章不聊玄学,只聊文件、目录、路径、inode、链接、权限和时间戳这些实实在在的东西。希望你看完之后,对这些基础概念有更踏实的把握。
一、一切皆文件 —— Linux 文件哲学
1.1 什么是“一切皆文件”
在 Linux 圈子里,“一切皆文件”这句话都快被说烂了。刚接触的时候我也觉得故弄玄虚——键盘是键盘,硬盘是硬盘,怎么能全叫文件呢?直到自己写多了服务端程序,才品出味来。
Linux 并不是说所有东西在物理上都是普通文本文件,而是尽量通过统一的文件接口来访问各种资源。
程序处理普通文件时,通常用这样一套系统调用:
open()
read()
write()
close()
读取终端输入,思路差不多。访问串口、读写管道、套接字甚至某些内核接口,仍然可以用类似的文件描述符模型。
应用程序
│
│ open / read / write / close
▼
文件描述符
│
├── 普通文件
├── 目录
├── 设备文件
├── 管道
├── 套接字
└── 内核虚拟文件
这套设计的最大好处不是“哲学听起来高级”,而是接口统一。程序没必要给每类设备重新发明一套读写逻辑,Shell 也可以把不同对象组合起来。
cat access.log | grep "500" | wc -l
这里的 cat 从磁盘文件读数据,grep 从管道读,wc 再从另一个管道读,它们根本不关心上游到底是文件、终端输入还是其他程序。这就是 Unix 和 Linux 工具链灵活的根本原因——一个工具专心做好一件事,再靠文件描述符和管道把它们拼接起来。看着朴素,组合起来杀伤力却很强。
当然,“一切皆文件”也不能理解得太死。设备往往还需要 ioctl 这类额外接口,网络套接字也有自己的连接语义。Linux 做的是尽量统一,不是把所有东西都粗暴地压成同一个形状。
1.2 Linux 七种文件类型
执行下面的命令时,输出第一列的第一个字符就是文件类型。
ls -l
示例结果大概是这样的:
-rw-r--r-- 1 dev dev 1234 Jul 20 10:30 app.log
drwxr-xr-x 2 dev dev 4096 Jul 20 10:20 config
lrwxrwxrwx 1 dev dev 12 Jul 20 10:18 current -> release-2026
最开头的字符不是权限位,它代表文件类型。
┌────────┬────────────┬────────────────────────────┐
│ 字符 │ 文件类型 │ 常见对象 │
├────────┼────────────┼────────────────────────────┤
│ - │ 普通文件 │ 文本、图片、程序、压缩包 │
│ d │ 目录文件 │ /etc、/home、项目目录 │
│ l │ 符号链接 │ 版本链接、快捷入口 │
│ c │ 字符设备 │ 终端、串口、随机数设备 │
│ b │ 块设备 │ 硬盘、分区、光盘设备 │
│ p │ 命名管道 │ 进程间单向数据传输 │
│ s │ 套接字 │ 本地进程通信、服务端接口 │
└────────┴────────────┴────────────────────────────┘
普通文件的类型字符是 -。Linux 不会傻乎乎地只看扩展名就判断文件类型。没有 .txt 后缀的文件照样可能是文本,一个叫 photo.jpg 的东西也可能只是被人改了名字。想知道真实类型,最好用 file 命令。
file README
file server
file image.png
目录的类型字符是 d。目录本质上也是文件,只不过里面存的不是正文,而是文件名与 inode 编号的对应关系。这个概念后面讲到 inode 的时候还会碰头。
符号链接的类型字符是 l,有点像 Windows 的快捷方式,但本质不同:它是一个独立文件,里面保存着目标路径。
字符设备的类型字符是 c,按字节流处理数据,终端就是典型。
ls -l /dev/tty
块设备的类型字符是 b,像硬盘、分区,可以按数据块随机访问。
ls -l /dev/sda
ls -l /dev/nvme0n1
命名管道的类型字符是 p,常用于进程间通信。先用 mkfifo message.pipe 建一个,一端 cat message.pipe 等着,另一端 echo "hello linux" > message.pipe 往里写,数据就流过去了。
套接字文件的类型字符是 s。MySQL、Docker、Nginx、systemd 等都可能通过 Unix Domain Socket 在本机通信。
ls -l /var/run/docker.sock
套接字文件虽然看着是文件,却不能拿 cat 随便读,它代表的是一个通信端点。
二、文件系统的骨架 —— 树形目录结构
Linux 所有目录和文件,都源自唯一的根目录 /。
2.1 从根开始的树
Linux 没有盘符那一套。 整个文件系统从唯一的根目录 / 开始,所有磁盘、分区、移动设备和网络文件系统都挂到这棵目录树的某个位置。
/
├── boot
├── dev
├── etc
├── home
│ ├── alice
│ └── bob
├── mnt
│ └── data
├── proc
├── sys
├── usr
└── var
假设服务器有一块系统盘和一块数据盘。
系统盘分区 /dev/nvme0n1p2
│
└── 挂载到 /
数据盘分区 /dev/sdb1
│
└── 挂载到 /data
挂载完成后,用户访问 /data,实际就是在访问 /dev/sdb1 上的文件系统。
mount /dev/sdb1 /data
这就是挂载点。挂载点必须是目录。挂载以后,该目录原本的内容会被暂时“盖住”,并不是删掉了,只是卸载前暂时看不到。这个坑我亲身踩过——新磁盘直接挂到一个有文件的目录上,眼前瞬间一片空白,差点当场写离职申请。后来 umount 一卸,原文件又出现了,原来是虚惊一场。
用 lsblk 可以很方便地查看块设备和挂载关系。
lsblk
典型输出:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 200G 0 disk
├─nvme0n1p1 259:1 0 1G 0 part /boot
└─nvme0n1p2 259:2 0 199G 0 part /
sdb 8:16 0 2T 0 disk
└─sdb1 8:17 0 2T 0 part /data
加上文件系统类型和 UUID 更实用:
lsblk -f
完整目录树和挂载关系可以这样理解:
/
├── boot ← 单独的启动分区
├── dev ← devtmpfs
├── etc
├── home
├── proc ← proc 虚拟文件系统
├── run ← tmpfs
├── sys ← sysfs 虚拟文件系统
├── data ← /dev/sdb1
├── usr
└── var
目录看着像普通目录,背后却可能来自完全不同的磁盘,甚至来自网络。df -hT 能查看各挂载点使用的文件系统类型和空间。
2.2 FHS 标准 —— 目录的“规矩”
不同发行版如果各玩各的目录,写脚本的人真的要崩溃。FHS(Filesystem Hierarchy Standard)就是来给这件事立规矩的——它规定了根目录下该放哪些核心目录,/usr 和 /var 内部又该怎么组织。有这套共识,至少不会乱得离谱。
这几年目录结构还有一个明显趋势:/usr 合并。传统结构里,根下有一套基础命令目录(/bin、/sbin、/lib),/usr 下又有一套(/usr/bin、/usr/sbin、/usr/lib)。现在很多现代发行版已经把它们统一了。
ls -ld /bin /sbin /lib /lib64
在现代 Ubuntu、Debian、Fedora 等系统中,你看到的很可能是符号链接:/bin -> usr/bin,/sbin -> usr/sbin。这样做的好处是程序文件都集中到 /usr,根目录结构更清爽。
传统结构
/
├── bin
├── sbin
├── lib
└── usr
├── bin
├── sbin
└── lib
现代结构
/
├── bin ────────> /usr/bin
├── sbin ────────> /usr/sbin
├── lib ────────> /usr/lib
└── usr
├── bin
├── sbin
└── lib
所以,别死背那些“/bin 只放普通用户命令,/sbin 只放管理员命令”的老教条,先看看你眼前系统的真实结构再说。
2.3 核心目录逐一解读
/bin:传统上放基础命令,现代系统多为 /usr/bin 的符号链接。
/sbin:传统上放系统管理命令,现在多指向 /usr/sbin。
/etc:系统级配置文件的集中地,passwd、fstab、ssh/sshd_config、nginx/nginx.conf 等等都在这里。这里该放文本配置,不该扔二进制文件或运行日志。
/dev:设备文件的家,/dev/null(黑洞)、/dev/zero、/dev/random 等都由内核动态创建。
/home:普通用户的家目录,通常这里约定,但不是铁律。
/root:root 用户的家目录,千万注意别和根目录 / 搞混。它单独放在根文件系统里,即便 /home 没挂载,管理员也能登录维护。
/var:放经常变化的数据:/var/log(日志)、/var/lib(应用状态)、/var/cache、/var/spool 等。生产服务器磁盘爆满,十有六七都能在 /var 找到“凶手”。
/usr:现代 Linux 用户空间程序和只读共享数据的核心位置。别简单理解成“某个用户的目录”,普通用户自己的东西在家目录。/usr/local 通常留给管理员手工安装的软件,防止被包管理器覆盖。
/tmp:临时文件,很多系统重启后会清空。通常开放给所有用户创建,但不能乱删别人文件,这依赖 t 权限(Sticky Bit)。
ls -ld /tmp # 通常看到 drwxrwxrwt
/proc:内核虚拟文件系统,不占磁盘空间,把进程和内核状态以文件形式暴露出来。/proc/cpuinfo、/proc/meminfo、/proc/1234/ 等都是。很多监控工具的本质就是在读 /proc。
/sys:同样是虚拟文件系统,主要展示设备、驱动、内核对象等。运维手动操作不多,但 udev 和性能调优都依赖它。
/boot:内核、initramfs、grub 等启动文件。别随手清,清理旧内核要老老实实用包管理器。
/run:本次启动的运行时数据(tmpfs),PID 文件、套接字、锁文件都很适合放这里,重启即消失。
核心目录速查图:
/
├── bin 基础命令,现代系统多指向 /usr/bin
├── sbin 管理命令,现代系统多指向 /usr/sbin
├── etc 系统配置
├── dev 设备文件
├── home 普通用户家目录
├── root root 用户家目录
├── var 日志、缓存、状态和变化数据
├── usr 应用程序、库和共享资源
├── tmp 临时文件
├── proc 进程与内核信息
├── sys 设备与内核对象
├── boot 内核与启动文件
└── run 本次启动的运行时数据
三、在树中定位 —— 路径的艺术
3.1 绝对路径与相对路径
绝对路径从根目录 / 开始,/etc/nginx/nginx.conf 这种,只要文件系统结构不变,从哪儿访问都指向同一个位置。脚本、配置文件、systemd service 文件里,强烈建议用绝对路径——因为你永远不知道脚本会从哪个目录被启动。
在脚本里可以这样安全地动态生成绝对路径:
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config/app.yaml"
相对路径从当前工作目录开始,交互操作时很方便,但前提是你得清楚自己此刻站在哪儿。一个 pwd 下去,心里就有数了。
绝对路径更稳,相对路径更快;脚本用前者,手工敲命令用后者。
3.2 特殊目录符号
. 当前目录。执行 ./start.sh 就是为了明确告诉 Shell:从当前目录运行。
.. 上级目录。cd ..,多个 .. 连用 cd ../../ 可以跳多级。
~ 当前用户的家目录。cd ~,cd ~/projects,还能 cd ~alice。
- 上一个工作目录。cd /var/log 再 cd /etc/nginx 后,一个 cd - 就回到 /var/log,来回切换特别顺手。
文件名里带空格时,要用引号或反斜杠转义。
cat "my file.txt"
cat my\ file.txt
文件名以 - 开头更麻烦,因为命令容易把它当选项。正确姿势是用 -- 或显式写 ./。
rm -- -danger.txt
rm ./-danger.txt
四、文件的身份证 —— inode 与链接
4.1 inode 才是文件的真正身份
很多人下意识觉得文件名就等于文件。但在 Linux 文件系统里,文件名更像一个“入口”,文件真正的元数据全登记在 inode 里。目录表里存放的,只是文件名与 inode 编号的映射。
目录项
┌───────────────┬──────────────┐
│ 文件名 │ inode 编号 │
├───────────────┼──────────────┤
│ report.txt │ 105278 │
│ app.log │ 105311 │
└───────────────┴──────────────┘
inode 里记录着文件类型、权限、所有者、大小、链接计数、时间戳、数据块位置等信息。文件名不在 inode 里,在目录项里;文件内容在数据块里。
用 ls -i 看 inode 编号,用 stat 看完整状态。
ls -i report.txt
stat report.txt
4.2 硬链接
硬链接就是多个目录项指向同一个 inode。创建方式是 ln source.txt backup.txt。两个文件的 inode 编号相同,链接计数从 1 变成 2。
source.txt ──┐
├── inode 105278 ── 数据块
backup.txt ──┘
修改任意一个文件,另一个立刻同步变化,因为它们就是同一个文件,只不过有多个名字。
硬链接的几个限制:
- 不能跨文件系统,inode 只在当前文件系统内部有意义。
- 通常不能链接目录(系统不允许随便制造目录环路)。
- 所有硬链接完全平等,没有主次之分。
删除操作:每删一个文件名,链接计数减一。只有链接计数归零且没有进程打开文件时,inode 和数据块才会真正释放。
4.3 符号链接
符号链接(软链接)是一个独立的文件,里面保存目标路径。
ln -s source.txt shortcut.txt
两个 inode 不同,shortcut.txt 的内容只是字符串 source.txt。符号链接可以跨文件系统,也可以指向目录,非常灵活。但原目标一旦被删除、移动或改名,软链接就会失效,直接报“No such file or directory”。
相对软链接的解析基准不是你的当前目录,而是软链接自身的所在目录。这一点曾让不少人迷糊过。
4.4 硬链接与软链接的区别
| 对比项目 |
硬链接 |
符号链接 |
| inode |
与目标相同 |
拥有独立 inode |
| 保存内容 |
直接指向同一文件 |
保存目标路径 |
| 跨文件系统 |
不支持 |
支持 |
| 链接目录 |
通常不支持 |
支持 |
| 删除原文件名 |
不受影响 |
链接失效 |
| 修改内容 |
所有链接共同变化 |
实际修改目标 |
| 文件大小 |
与目标一致 |
通常是路径字符串长度 |
| 常见用途 |
同文件多入口 |
快捷入口、版本切换 |
硬链接不是备份,软链接也不是备份。它们解决的是路径引用问题,不是数据冗余问题。
4.5 文件删除的底层真相
执行 rm 时,Linux 通常不是立刻把数据擦掉,而是调用类似 unlink 的操作,删掉目录项,再把 inode 的链接计数减一。只有链接计数归零,并且没有进程继续占用文件时,系统才真正释放空间。
执行 rm report.log
│
▼
删除目录项 report.log
│
▼
inode 链接计数减一
│
├── 仍大于 0
│ 文件继续存在
│
└── 等于 0
│
▼
检查是否仍被进程打开
│
┌──────┴──────┐
│ │
没有 仍然打开
│ │
释放 inode 和数据 等进程关闭后再释放
这就是为什么经常出现日志文件明明删了,磁盘空间却不回来的原因——服务进程还握着文件描述符,继续往已删除的 inode 里写数据。排查这类问题用:
lsof | grep deleted
lsof +L1
输出类似:
java 2318 app 12w REG 253,0 21474836480 0 105311 /var/log/app.log (deleted)
正确的做法是通过 systemctl reload 或重启让进程重新打开日志文件。谨慎的做法是通过 /proc/PID/fd/ 清空内容,但务必看准 PID 和文件描述符。应对这种场景,更成熟的方案是配合 logrotate 和合理的重载机制。
五、文件的通行证 —— 权限管理
5.1 读、写、执行三种权限
ls -l 输出的权限串可以这样拆开看:
- rwx r-x r--
│ │ │ │
│ │ │ └── 其他用户 o
│ │ └───────── 所属组 g
│ └──────────────── 所有者 u
└────────────────────── 文件类型
三种基础权限对应的数字:r=4, w=2, x=1。常见的 755 就是所有者 rwx,所属组 r-x,其他人 r-x。
- 普通文件:
r 读内容,w 改内容,x 执行程序/脚本。
- 脚本还需要正确的 shebang,如
#!/usr/bin/env bash。
5.2 目录权限为什么不一样
这是最容易踩坑的地方。 目录的 r 允许读取目录项(看到文件名);x 允许“穿过”目录去访问里面的文件;w 允许增删改名,但一般需要配合 x。
如果目录只有 r 没有 x,你会看到文件名,但 ls -l 出来全是问号。因为 ls 能读目录项,却无法对文件执行 stat 穿过目录。
能不能删除文件,主要看父目录权限,而不是文件自身的 w。 即使文件是只读的,只要你对父目录有 w 和 x,通常就能删。反过来,文件可写,但父目录不给 w,你能改内容但无法删或改名。
排查逐级权限的神器:namei -l /srv/web/config/app.yaml。
5.3 修改权限与所有者
数字法一次性设置完整权限,符号法用于局部微调。
chmod 755 script.sh
chmod u+x script.sh
递归改目录务必小心,别一刀切把所有普通文件都加上了执行权限。更合理的做法是分开:
find project -type d -exec chmod 755 {} \;
find project -type f -exec chmod 644 {} \;
修改所有者用 chown,修改所属组用 chgrp。
chown alice:developers report.txt
chgrp developers report.txt
把 chmod 777 当万能药是不可取的。 它能让 Permission denied 暂时消失,也让所有用户拥有了所有权限。服务器上这么干,等于修门锁的时候直接把门拆了。
5.4 默认权限与 umask
新建文件的默认权限通常从 666 开始(不带执行),目录从 777 开始(需要执行位)。umask 会按位清除相应的权限。
比如 umask=022,目录结果是 777 掩掉 ----w--w- 得到 755,文件结果是 666 掩掉后得到 644。严格说不是做十进制减法,而是做位掩码清除。
常用 umask 值:
022:目录 755,文件 644,适合普通环境。
002:目录 775,文件 664,适合团队共享。
077:目录 700,文件 600,适合极高安全要求。
umask 只影响新创建的文件,更改后需写入 /etc/profile、用户 Shell 配置或 systemd 服务文件才能永久生效。
5.5 特殊权限与 ACL
- SUID:可执行文件的所有者执行位变成
s,程序运行时临时获得文件所有者的权限(如 passwd)。风险高,有漏洞的程序可能成为提权口。
- SGID:用在目录上时,新建文件会自动继承目录的所属组,非常适合团队共享。
- Sticky Bit:共享目录尾部出现
t,所有人都可以创建文件,但只能删除自己的。
ACL 可以给指定用户或组单独授权,突破三类身份的限制。
setfacl -m u:alice:rw report.txt
setfacl -m d:u:alice:rwx shared # 默认 ACL,新文件继承
注意 ACL mask,它可能把你明明给了 rwx 的权限给限缩成 r-x,碰到奇怪现象,先用 getfacl 看一眼。
六、动手实践 —— 文件与目录常用命令
6.1 浏览与导航
ls -lah # 长格式、隐藏文件、人性化大小
ls -lt # 按修改时间排序
ls -lhS # 按大小排序
cd /etc
cd ..
cd ~
cd - # 返回上一个目录
pwd
tree -L 2 # 树形展示,限制层级
有些环境没装 tree,可以 sudo apt install tree 或 dnf install tree。
6.2 创建与删除
mkdir -p project/{src,config,logs,data,tmp} # 一条命令创建多组目录
touch app.log
rm app.log
rm -r project
rm -rf project # 务必再三确认路径
我平时执行危险删除前,会先打印路径确认,脚本里还会检查变量是否为空:
if [ -z "$TARGET_DIR" ] || [ "$TARGET_DIR" = "/" ]; then
echo "拒绝删除危险路径"
exit 1
fi
rm -rf -- "$TARGET_DIR"
删除空目录用 rmdir,目录非空时它会拒绝,这种“胆小”反而让人更安心。
6.3 复制、移动与重命名
cp source.txt target.txt
cp -r project project-backup
cp -p app.conf app.conf.bak # 保留时间戳和权限
cp -a source/ target/ # 归档模式
rsync -aHAX source/ target/ # 大量文件迁移,保留硬链接、ACL、扩展属性
mv old.txt new.txt
mv app.log archive/
同一文件系统内 mv 只改目录项,非常快;跨文件系统则接近“复制后删除”。
6.4 查看文件内容
cat app.conf # 短文件直接看
less app.log # 大文件分页,支持 / 搜索、n 跳转
head -n 20 app.log # 前 20 行
tail -n 100 app.log # 末 100 行
tail -f app.log # 实时追踪
tail -F app.log # 日志轮转后自动重新打开
more 还能分页,但交互能力不如 less,属于老古董了。
6.5 查找文件与目录
find 是实时遍历,功能强大但慢;locate 基于数据库,极快但可能滞后。
find /var/log -name "*.log"
find /var/log -type f -mtime -1
find /tmp -type f -mmin -60
find /srv -type f -perm 0777
find /tmp -type f -mtime +7 -exec rm -f {} \; # 先换成 -exec echo {} \; 试运行
which 找可执行文件,type 能告诉你某个命令到底是别名、内建还是外部程序。whereis 查二进制、源码、手册路径。grep 搜索文件内容:
grep -rn "ERROR" /var/log/
grep -i "warn" app.log
grep -F "a.b.c" config.txt # 固定字符串匹配
6.6 链接与查看
ln source.txt hard-link.txt
ln -s source.txt soft-link.txt
ln -sfn /opt/app-2.0 /opt/app # 强制更新符号链接
file app # 识别真实文件类型
stat app # 查看完整元数据
stat -c '%n %i %s %a %U %G' app
七、文件的时间属性
7.1 三种时间戳与出生时间
- atime:最后读取内容的时间(受
relatime、noatime 挂载策略影响,不一定每次访问都更新)。
- mtime:最后修改内容的时间,
ls -l 默认显示的就是它。
- ctime:最后修改 inode 状态的时间(chmod、chown、写入内容等都可能更新)。ctime 不是创建时间,那个
c 更接近“change”。
- birth time / crtime:真正的创建时间,需要文件系统支持,很多老工具或系统直接显示
-。
7.2 使用 stat 查看时间信息
stat report.txt
输出可能包含 Access、Modify、Change、Birth。ls -l 默认看 mtime,ls -lu 看 atime,ls -lc 看 ctime。但完整分析还是 stat 最实在。
7.3 使用 touch 修改时间戳
touch report.txt # 更新 atime 和 mtime 到当前
touch -a report.txt # 只改 atime
touch -m report.txt # 只改 mtime
touch -t 202607201530 report.txt
touch -r template.txt report.txt # 参照另一个文件的时间
注意:用 touch 修改 atime 或 mtime 时,ctime 仍会被系统更新为当前时间,没法像 mtime 那样任意设回历史值。
7.4 时间戳的实际用途
增量备份依赖 mtime 和大小(rsync -av 默认快速检查),find 结合 -mtime、-mmin 做文件清理,logrotate 依赖时间戳判断轮转周期。时间戳不是摆设,是很多自动化工具运转的基石。
八、综合实战 —— 从零搭建项目与常见错误排查
8.1 搭建 Web 项目目录
假设部署一个叫 myweb 的服务,设计成这样的结构:
/srv/myweb
├── current -> releases/20260731
├── releases
│ ├── 20260720
│ └── 20260731
├── shared
│ ├── config
│ ├── data
│ ├── logs
│ └── uploads
└── tmp
创建目录、发布版本、共享目录链接、切换当前版本:
sudo mkdir -p /srv/myweb/{releases,shared/{config,data,logs,uploads},tmp}
sudo mkdir -p /srv/myweb/releases/20260731
sudo rsync -a ./dist/ /srv/myweb/releases/20260731/
sudo ln -s /srv/myweb/shared/config /srv/myweb/releases/20260731/config
sudo ln -s /srv/myweb/shared/uploads /srv/myweb/releases/20260731/uploads
sudo ln -sfn /srv/myweb/releases/20260731 /srv/myweb/current
回滚也只是重新原子更新 current 链接,非常快。不过真实部署还要考虑进程重启、健康检查等,但符号链接帮我们把版本路径管理做得非常干净。
8.2 权限规划与设置
创建专用用户和组,设置目录所有权,用 SGID 和 ACL 保证新文件归属。
sudo groupadd myweb
sudo useradd -r -g myweb -d /srv/myweb -s /usr/sbin/nologin myweb
sudo usermod -aG myweb deploy
sudo chown -R myweb:myweb /srv/myweb
sudo chmod -R g+rwX /srv/myweb # 大写 X 更稳
sudo find /srv/myweb -type d -exec chmod g+s {} \;
sudo setfacl -R -m g:myweb:rwx /srv/myweb
sudo setfacl -R -d -m g:myweb:rwx /srv/myweb
配置文件加严:chmod 640 /srv/myweb/shared/config/app.env;日志目录用 chmod 2750;上传目录用 2770。团队协作时把 umask 002 加进部署脚本。
8.3 路径导航与软链接
日常排错时,cd /srv/myweb/current 快速进入当前版本,readlink -f /srv/myweb/current 看清真实路径。ls -l /srv/myweb/current 看链接本身,末尾加 / 则查看目录内容。
8.4 常见错误排查
- Permission denied:依次用
ls -l、namei -l、getfacl、findmnt 排查权限、路径执行位、ACL 和只读状态;有 SELinux 时别忘了 ausearch -m AVC。别上来就 chmod 777。
- No such file or directory:先核对路径、检查符号链接是否失效,再看看脚本的解释器路径是否存在、换行符是否有 CRLF。
- No space left on device:
df -h 看磁盘,du -xhd1 /var | sort -h 逐级定位大目录;若 df 和 du 差距大,多半是“已删除但仍被进程占用的文件”在作怪,用 lsof +L1 定位。
- df 显示有空间却报空间不足:极可能是
df -i 看到 inode 耗尽,清理海量小文件。
- Device or resource busy:卸载前先
fuser -mv /mnt/data 或 lsof +D /mnt/data 看谁在占用,也别忘记检查当前 Shell 是否刚好站在该目录里。懒卸载 umount -l 适用于恢复场景,但别当万能按钮。
- Operation not permitted:root 都删不掉?用
lsattr 看看文件是否被设了不可变属性 i,有就去掉:sudo chattr -i file。
错误排查速查表:
| 错误提示 |
优先排查命令 |
处理要点 |
| Permission denied |
ls -l、namei -l、getfacl |
修权限、补目录 x、查 ACL |
| No such file or directory |
readlink -f、file、head -n 1 |
修路径、重建链接、换行符 |
| No space left on device |
df -h、du -xhd1 |
清理大文件、迁移或扩容 |
| 有空间却提示空间不足 |
df -i |
定位并清理海量小文件 |
| 删除日志后空间不释放 |
lsof +L1 |
重载或重启进程 |
| Device or resource busy |
fuser -mv、lsof +D |
退出目录、结束进程或懒卸载 |
| Operation not permitted |
lsattr |
chattr -i 去掉不可变属性 |
8.5 日志目录迁移实战
假设 /var 分区告急,新磁盘挂载在 /data,需要把 /var/log 迁过去。这类操作最好在维护窗口进行:
-
确认磁盘:lsblk -f && df -hT /data
-
创建目标:sudo mkdir -p /data/var-log
-
在线预同步:sudo rsync -aHAX /var/log/ /data/var-log/
-
停相关服务(如 systemctl stop nginx myapp),做最终同步:
sudo rsync -aHAX --delete /var/log/ /data/var-log/
-
备份原目录并创建软链接:
sudo mv /var/log /var/log.old
sudo ln -s /data/var-log /var/log
-
验证 ls -ld /var/log 和 readlink -f /var/log,然后启动服务,tail -F /var/log/syslog 观察日志正常产出。
-
配置 /etc/fstab 确保新磁盘开机自动挂载(使用 UUID,加 nofail)。
更稳的方案是直接把新文件系统挂载到 /var/log 或用 bind mount,避免长期依赖软链接。
8.6 链接的妙用与误区
软链接非常适合版本切换:/opt/nginx -> /opt/nginx-1.28.0。升级时 ln -sfn /opt/nginx-1.29.0 /opt/nginx,所有业务配置都不需要改。
硬链接看起来像“备份”,实则大坑。 ln report.txt report-backup.txt 后,修改 report.txt,report-backup.txt 跟着变;清空其中一个,另一个也空了。它只能防止误删一个名字,但对内容损坏、文件系统故障、勒索软件毫无抵抗力。真正的备份,必须生成独立的数据副本,cp -a、rsync -aHAX --delete,再配合版本控制(git),最好有 3-2-1 策略(三份数据、两种介质、一份异地)。
附录 A 常用命令速查表
| 目标 |
命令 |
| 查看当前目录 |
pwd |
| 列出文件 |
ls -lah |
| 显示 inode |
ls -li |
| 切换目录 |
cd /path |
| 返回上级 |
cd .. |
| 返回上一个目录 |
cd - |
| 创建目录 |
mkdir dir |
| 创建多级目录 |
mkdir -p a/b/c |
| 创建空文件 |
touch file |
| 删除文件 |
rm file |
| 删除目录 |
rm -r dir |
| 删除空目录 |
rmdir dir |
| 复制文件 |
cp src dst |
| 保留属性复制 |
cp -a src dst |
| 移动或改名 |
mv old new |
| 查看短文件 |
cat file |
| 分页查看 |
less file |
| 查看开头 |
head -n 20 file |
| 查看结尾 |
tail -n 100 file |
| 实时看日志 |
tail -F file |
| 查找文件 |
find /path -name "*.log" |
| 快速按名称查找 |
locate filename |
| 搜索内容 |
grep -rn "text" /path |
| 查看命令类型 |
type command |
| 查看程序路径 |
which command |
| 查看文件类型 |
file filename |
| 查看详细属性 |
stat filename |
| 创建硬链接 |
ln source link |
| 创建软链接 |
ln -s target link |
附录 B FHS 核心目录清单
| 目录 |
常见用途 |
/ |
整个文件系统的根 |
/bin |
基础用户命令,现代系统常链接到 /usr/bin |
/sbin |
系统管理命令,现代系统常链接到 /usr/sbin |
/boot |
内核、initramfs、引导程序 |
/dev |
设备文件 |
/etc |
系统级配置 |
/home |
普通用户家目录 |
/root |
root 用户家目录 |
/lib |
基础共享库,现代系统常链接到 /usr/lib |
/media |
可移动介质自动挂载位置 |
/mnt |
临时手动挂载位置 |
/opt |
附加或第三方软件 |
/proc |
进程与内核虚拟文件系统 |
/run |
本次启动的运行时数据 |
/srv |
服务对外提供的数据 |
/sys |
设备与内核对象虚拟文件系统 |
/tmp |
短期临时文件 |
/usr |
用户空间程序、库和共享资源 |
/usr/local |
本机管理员手动安装的软件 |
/var |
日志、缓存、队列和动态数据 |
/var/lib |
应用持久状态数据 |
/var/log |
系统和应用日志 |
/var/cache |
可重新生成的缓存 |
/var/tmp |
保留时间通常长于 /tmp 的临时文件 |
附录 C 权限数字与 umask 示例
基础数字:r=4,w=2,x=1;常见组合:7=rwx,6=rw-,5=r-x,4=r--。常见权限:
600 rw------- 私密文件
640 rw-r----- 服务配置
644 rw-r--r-- 普通文本文件
700 rwx------ 私密目录或脚本
750 rwxr-x--- 服务目录
755 rwxr-xr-x 普通目录和公开程序
775 rwxrwxr-x 组协作目录
umask=022 → 目录 755,文件 644;002 → 775,664;077 → 700,600。特殊权限数字:4 SUID,2 SGID,1 Sticky Bit。
附录 D find 常用组合
find /path -name "*.conf"
find /path -iname "*.jpg"
find /path -type f # 只查文件
find /path -type d # 只查目录
find /path -type f -size +500M
find /path -type f -mmin -60 # 1小时内修改
find /path -type f -mtime +7 # 7天前修改
find /path -type f -empty
find /path -user alice
find /path -type f -perm -0002 # 全局可写
find /tmp/myapp -type f -mtime +7 -delete # 先换成 -print 预览
附录 E 三种时间戳对比
| 时间属性 |
含义 |
常见触发操作 |
| atime |
最后读取内容的时间 |
cat、程序读取 |
| mtime |
最后修改内容的时间 |
写入、追加、截断 |
| ctime |
最后修改 inode 状态的时间 |
chmod、chown、写入、链接变化 |
| birth time |
文件创建时间(部分系统支持) |
创建文件时 |
附录 F 常见错误速查
| 现象 |
优先检查 |
| 无法读取文件 |
文件 r、路径中各目录的 x |
| 无法进入目录 |
目录 x |
| 无法列出目录名 |
目录 r |
能看文件名但 ls -l 全是问号 |
目录有 r 但没有 x |
| 无法删除文件 |
父目录缺少 w 或 x |
| root 也删不掉文件 |
lsattr 检查不可变属性 |
| 磁盘空间满 |
df -h |
| inode 用完 |
df -i |
| 删除日志后空间不变 |
lsof +L1 |
| 挂载点无法卸载 |
fuser -mv |
| 符号链接失效 |
readlink -f |
| 脚本存在却无法执行 |
检查解释器、换行符和执行权限 |
| 配置修改后服务仍读旧内容 |
检查真实路径、符号链接和进程是否重载 |